天天看點

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

作者:85後程式猿

上一篇咱們說到doProcessConfigurationClass方法來進行處理,在該方法中會針對@Component、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean注解的配置類分别處理,這一篇幅咱們詳細分析doProcessConfigurationClass方法的具體邏輯,先看代碼:

protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
      throws IOException {

   //關鍵點一
   if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
      // Recursively process any member (nested) classes first
      processMemberClasses(configClass, sourceClass);
   }

   // 關鍵點二
   for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), PropertySources.class,
         org.springframework.context.annotation.PropertySource.class)) {
      if (this.environment instanceof ConfigurableEnvironment) {
         processPropertySource(propertySource);
      }else {
         logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +
               "]. Reason: Environment must implement ConfigurableEnvironment");
      }
   }

   //關鍵點三
   Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
         sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
   if (!componentScans.isEmpty() &&
         !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
      for (AnnotationAttributes componentScan : componentScans) {
         Set<BeanDefinitionHolder> scannedBeanDefinitions =
               this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

         // Check the set of scanned definitions for any further config classes and parse recursively if needed
         for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
            BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
            if (bdCand == null) {
               bdCand = holder.getBeanDefinition();
            }
					  if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
               parse(bdCand.getBeanClassName(), holder.getBeanName());
            }
         }
      }
   }

   // Process any @Import annotations
   // 關鍵點四
   processImports(configClass, sourceClass, getImports(sourceClass), true);

   // Process any @ImportResource annotations
   // 關鍵點五
   AnnotationAttributes importResource =
         AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
   if (importResource != null) {
      String[] resources = importResource.getStringArray("locations");
      Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
      for (String resource : resources) {
         String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
         configClass.addImportedResource(resolvedResource, readerClass);
      }
   }

   // Process individual @Bean methods
   // 關鍵點六
   Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
   for (MethodMetadata methodMetadata : beanMethods) {
      configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
   }

   // Process default methods on interfaces
   // 關鍵點七
   processInterfaces(configClass, sourceClass);

   // Process superclass, if any
   // 關鍵點八
   if (sourceClass.getMetadata().hasSuperClass()) {
      String superclass = sourceClass.getMetadata().getSuperClassName();
      if (superclass != null && !superclass.startsWith("java") &&
            !this.knownSuperclasses.containsKey(superclass)) {
         this.knownSuperclasses.put(superclass, configClass);
         // Superclass found, return its annotation metadata and recurse
         return sourceClass.getSuperClass();
      }
   }
   return null;
}           

關鍵點一

針對有@Component注解的,調用processMemberClasses進行處理,該方法中擷取内部類, 檢視内部類上是否存在Component、ComponentScan、Import、ImportResource注解,如果存在則調用processConfigurationClass方法進行處理,說白了就内部内也是需要處理的。

關鍵點二

解析@PropertySource注解,這裡Spring會去解析注解所指定的properties檔案,并添加到environment中去。

關鍵點三

處理@ComponentScans、@ComponentScan注解,針對這兩個注解,Spring會調用this.componentScanParser.parse方法掃描注解中指定目錄下的所有類,得到BeanDefinition集合,然後調用checkConfigurationClassCandidate方法檢查掃描所得到BeanDefinition是不是配置Bean。這裡我們對componentScanParser.parse詳細介紹下:

public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {

   ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
         componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

   Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
   boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
   scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
         BeanUtils.instantiateClass(generatorClass));

   ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
   if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
      scanner.setScopedProxyMode(scopedProxyMode);
   }else {
      Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
      scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
   }

   scanner.setResourcePattern(componentScan.getString("resourcePattern"));

   for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
         scanner.addIncludeFilter(typeFilter);
      }
   }
   for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
      for (TypeFilter typeFilter : typeFiltersFor(filter)) {
         scanner.addExcludeFilter(typeFilter);
      }
   }

   boolean lazyInit = componentScan.getBoolean("lazyInit");
   if (lazyInit) {
      scanner.getBeanDefinitionDefaults().setLazyInit(true);
   }

   Set<String> basePackages = new LinkedHashSet<>();
   String[] basePackagesArray = componentScan.getStringArray("basePackages");
   for (String pkg : basePackagesArray) {
      String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
            ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
      Collections.addAll(basePackages, tokenized);
   }
   for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
      basePackages.add(ClassUtils.getPackageName(clazz));
   }
   if (basePackages.isEmpty()) {
      basePackages.add(ClassUtils.getPackageName(declaringClass));
   }

   scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
      @Override
      protected boolean matchClassName(String className) {
         return declaringClass.equals(className);
      }
   });

   return scanner.doScan(StringUtils.toStringArray(basePackages));
}           

這裡主要注意includeFilters和excludeFilters兩個主要是幹嘛的,就Spring而言哪些類的對象需要被Spring容器管理呢?一種是像@Component這些Spring定義的注解,除此之外Spring還允許咱們自己定義哪些注解類是需要被管理的,哪些注解類是一定不會被管理的,我們在注解ComponentScan上指定includeFilters用于指定需要被管理的,excludeFilters指定需要被排除的。

而basePackage和basePackages這兩個主要是指定要被掃描的類所在的路徑,調用scanner.doScan開始掃描(在該方法中又會調用findCandidateComponents方法去每個包路徑下去找)。掃描這個詞可能不好了解,其實就是循環去讀取包下面所有類的元資訊,啥是元資訊?比如類的全路徑名、有哪些注解等等構成了元資訊。當讀取到資訊,并且判斷isCandidateComponent後,會生成ScannedGenericBeanDefinition類型的BeanDefinition,後面我們單獨會講BeanDefinition的結構。掃描得到BeanDefinition後,該BeanDefinition會被添加到BeanFactory這個Bean工廠的beanDefinitionMap集合中。

關鍵點四

解析@Import,在使用@Import注解時,我們需要指定類,Spring對于這些指定的類有三種處理方式,下面來看代碼:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, boolean checkForCircularImports) {
	 ......
         for (SourceClass candidate : importCandidates) {
            if (candidate.isAssignable(ImportSelector.class)) {
               // Candidate class is an ImportSelector -> delegate to it to determine imports
               Class<?> candidateClass = candidate.loadClass();
               ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
                     this.environment, this.resourceLoader, this.registry);
               if (selector instanceof DeferredImportSelector) {
                  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
               }else {
                  String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
                  Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
                  processImports(configClass, currentSourceClass, importSourceClasses, false);
               }
            }else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) { 
               Class<?> candidateClass = candidate.loadClass();
               ImportBeanDefinitionRegistrar registrar =
                     ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                           this.environment, this.resourceLoader, this.registry);
               configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
            }else {
               this.importStack.registerImport(
                     currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
               processConfigurationClass(candidate.asConfigClass(configClass));
            }
  	 }
    ......
}           

注意這個方法的第三個參數importCandidates,這個是getImports方法的傳回值,是Import的參數值對應的類的元資訊。然後我們看上面的for循環,它是循環處理元資訊集合資料,這裡有三種處理方式:

1.如果import指定的類是一個ImportSelector的實作類,則會調用它的selectImports方法,該方法會傳回一個String數組,指定多個配置類。

2.如果import指定的類是一個ImportBeanDefinitionRegistrar的實作類,就把該類的執行個體放入importBeanDefinitionRegistrars中,後面再執行該執行個體的registerBeanDefinitions方法。

3.如果是一個普通的類,則當作普通的配置類處理。

以上是我們經常使用Import注解的場景,大家不防好好會議下是不是!

關鍵點五

解析@ImportResource,得到導入進來的spring的xml配置檔案,然後解析。這裡隻是添加到配置類的importedResources集合中。

關鍵點六

解析配置類中的加了@Bean注解的方法。這裡隻是添加到了配置類的beanMethods集合中,後續會進行處理。

關鍵點七

如果配置類實作了某個接口,那麼還會解析該接口中的加了@Bean注解的預設方法。

關鍵點八

如果有父類,則傳回父類給上層周遊進行處理。

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