天天看點

Spring架構系列之構造方法BeanDefinition掃描原理03

作者:85後程式猿

上一篇幅我們講到ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法,這個方法掃描得到所有的BeanDefinition,那具體是怎麼掃描得到所有BeanDefinition的呢?postProcessBeanDefinitionRegistry調用了一個核心方法:processConfigBeanDefinitions,代碼如下:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
   String[] candidateNames = registry.getBeanDefinitionNames();
  
   for (String beanName : candidateNames) {
      BeanDefinition beanDef = registry.getBeanDefinition(beanName);
      if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
         if (logger.isDebugEnabled()) {
            logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
         }
      }else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
          //關鍵點一
            configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
      }
   }

   // Return immediately if no @Configuration classes were found
   if (configCandidates.isEmpty()) {
      return;
   }

   // Sort by previously determined @Order value, if applicable
   configCandidates.sort((bd1, bd2) -> {
      int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
      int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
      return Integer.compare(i1, i2);
   });

   // Detect any custom bean name generation strategy supplied through the enclosing application context
   SingletonBeanRegistry sbr = null;
   if (registry instanceof SingletonBeanRegistry) {
      sbr = (SingletonBeanRegistry) registry;
      if (!this.localBeanNameGeneratorSet) {
        //關鍵點二
         BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
               AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
         if (generator != null) {
            this.componentScanBeanNameGenerator = generator;
            this.importBeanNameGenerator = generator;
         }
      }
   }

   if (this.environment == null) {
      this.environment = new StandardEnvironment();
   }

   // Parse each @Configuration class
   ConfigurationClassParser parser = new ConfigurationClassParser(
         this.metadataReaderFactory, this.problemReporter, this.environment,
         this.resourceLoader, this.componentScanBeanNameGenerator, registry);

   Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
   Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
   do {
     //關鍵點三
      parser.parse(candidates);
      parser.validate();

      Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
      configClasses.removeAll(alreadyParsed);

      // Read the model and create bean definitions based on its content
      if (this.reader == null) {
         this.reader = new ConfigurationClassBeanDefinitionReader(
               registry, this.sourceExtractor, this.resourceLoader, this.environment,
               this.importBeanNameGenerator, parser.getImportRegistry());
      }

      this.reader.loadBeanDefinitions(configClasses);
      alreadyParsed.addAll(configClasses);

      candidates.clear(); 
     
      if (registry.getBeanDefinitionCount() > candidateNames.length) {
         String[] newCandidateNames = registry.getBeanDefinitionNames();
         Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
         Set<String> alreadyParsedClasses = new HashSet<>();
         for (ConfigurationClass configurationClass : alreadyParsed) {
            alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
         }
         for (String candidateName : newCandidateNames) {
            if (!oldCandidateNames.contains(candidateName)) {
               BeanDefinition bd = registry.getBeanDefinition(candidateName);
               if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
                     !alreadyParsedClasses.contains(bd.getBeanClassName())) {
                  candidates.add(new BeanDefinitionHolder(bd, candidateName));
               }
            }
         }
         candidateNames = newCandidateNames;
      }
   }
   while (!candidates.isEmpty());

   // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
   if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
      sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
   }

   if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
      // Clear cache in externally provided MetadataReaderFactory; this is a no-op
      // for a shared cache since it'll be cleared by the ApplicationContext.
      ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
   }
}           

我們來分析下該方法的幾個關鍵點:

關鍵點一

調用ConfigurationClassUtils.checkConfigurationClassCandidate方法來檢查有哪些是配置類候選者?因為隻有有了配置類,Spring才直到如何進一步掃描。Spring是如何來确定哪些是配置類的呢?且看checkConfigurationClassCandidate方法的邏輯代碼:

public static boolean checkConfigurationClassCandidate(
			BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {
  ......
  Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
  if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {
     beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
  }else if (config != null || isConfigurationCandidate(metadata)) {
     beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
  }else {
     return false;
  }
  ......
}           

如上代碼,有三種類型的類是配置類:

  • 增加了Configuration注解的類,這類配置類的BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE屬性被設定為了CONFIGURATION_CLASS_FULL,還記得我們上一篇幅講的對于CONFIGURATION_CLASS_FULL這種配置類是需要特别處理的,對,就是在這裡辨別的。
  • 通過isConfigurationCandidate方法檢測的類,這類配置類的BeanDefinition的CONFIGURATION_CLASS_ATTRIBUTE屬性被設定為了CONFIGURATION_CLASS_LITE,是不是和上面不一樣。哪些是通過檢測的呢?看代碼:
private static final Set<String> candidateIndicators = new HashSet<>(8);

	static {
		candidateIndicators.add(Component.class.getName());
		candidateIndicators.add(ComponentScan.class.getName());
		candidateIndicators.add(Import.class.getName());
		candidateIndicators.add(ImportResource.class.getName());
	}
public static boolean isConfigurationCandidate(AnnotationMetadata metadata) {
   // Do not consider an interface or an annotation...
   if (metadata.isInterface()) {
      return false;
   }
   for (String indicator : candidateIndicators) {
      if (metadata.isAnnotated(indicator)) {
         return true;
      }
   }

   // Finally, let's look for @Bean methods...
   try {
      return metadata.hasAnnotatedMethods(Bean.class.getName());
   }catch (Throwable ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Failed to introspect @Bean methods on class [" + metadata.getClassName() + "]: " + ex);
      }
      return false;
   }
}           

看到了吧,加了Component、ComponentScan、Import、ImportResource注解的類,或者這個類裡面有方法加了Bean注解的類都是通過檢測的類。

關鍵點二

如果目前BeanFactory支援單例bean,則設定一個BeanNameGenerator,用來在掃描@Component和@Import某個Bean時取一個名字,這裡程式員可以通過調用applicationContext.setBeanNameGenerator方法來指定一個beanName生成器。

關鍵點三

parser.parse這個是核心中的核心,在"關鍵點一"中已經獲得了需要解析的配置類,然後這裡調用parse開始對配置BeanDefinition進行解析,在該方法中會周遊配置類的BeanDefinition,通過ASM這種位元組碼處理技術來得到資源資訊,注意這裡不是直接加載位元組碼到記憶體中,得到資源資訊後,調用doProcessConfigurationClass方法來進行處理,在該方法中會針對@Component、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean注解的配置類分别處理,由于篇幅原因,詳細我們下一篇介紹,本篇幅中大家隻要直到,這個方法就是掃描得到具體的BeanDefinition就可以了。

掃描完成後,目前這個注解類也是需要調用this.reader.loadBeanDefinitions(configClasses)方法注冊成為BeanDefinition的;

我們再來關注另外一個問題,這裡為什麼要用個do...while循環呢?

其實很簡單,因為咱們在掃描完成後,肯定會掃描到新的配置類,是以需要循環調用parse來對新的配置類進行處理。

Spring架構系列之構造方法BeanDefinition掃描原理03