超详细的 SpringBoot 自动装配源码分析

前言

在正式开始之前,不妨先思考几个关键的问题:

  • SpringBoot 如何确定哪些类是需要自动装配的?
  • 自动装配类大量用到了 @Condition 注解,那如何保证所有用户自定义的类先于自动配置类加载呢?
  • 如果自动装配的类需要知道用户代码的路径,该如何做呢?
  • @AutoConfigurationPackage 的作用和意义是什么?

带着这些问题,就开始读源码了。

本文基于 SpringBoot 2.7.2

@SpringBootApplication 注解

java 复制代码
 @SpringBootConfiguration
 @EnableAutoConfiguration
 @ComponentScan(excludeFilters = { 
     @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
     @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 public @interface SpringBootApplication {
     
     @AliasFor(annotation = EnableAutoConfiguration.class)
     Class<?>[] exclude() default {};
     
     @AliasFor(annotation = EnableAutoConfiguration.class)
     String[] excludeName() default {};
     
     @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
     String[] scanBasePackages() default {};
     
     @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
     Class<?>[] scanBasePackageClasses() default {};
     
     @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
     Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
     
     @AliasFor(annotation = Configuration.class)
     boolean proxyBeanMethods() default true;
 }

可以看到 @SpringBootApplication 有三个注解组合而成。

1、@ComponentScan:包扫描路径

@ComponentScan 是 Spring 的注解,用于扫描带有 @Component 等注解的类并注入到 Spring 容器中。

2、@SpringBootConfiguration:配置类

java 复制代码
 @Configuration
 @Indexed
 public @interface SpringBootConfiguration {
     @AliasFor(annotation = Configuration.class)
     boolean proxyBeanMethods() default true;
 }

我们发现 @SpringBootConfiguration 和 @Configuration 几乎没有差别。

javadoc 是这样说的:

Indicates that a class provides Spring Boot application @Configuration. Can be used as an alternative to the Spring's standard @Configuration annotation so that configuration can be found automatically (for example in tests). Application should only ever include one @SpringBootConfiguration and most idiomatic Spring Boot applications will inherit it from @SpringBootApplication.

大概就是,@SpringBootConfiguration 是专门为 SpringBoot 启动类定制的 @Configuration 注解,且每个 Spring Boot applications 只能有一个类有 @SpringBootConfiguration,我理解就是与普通的 @Configuration 做一个区分。

3、@EnableAutoConfiguration:开启自动配置功能

@EnableAutoConfiguration 是实现自动配置的核心,重点看:

java 复制代码
 @AutoConfigurationPackage
 @Import(AutoConfigurationImportSelector.class)
 public @interface EnableAutoConfiguration {
     String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
 ​
     // 排除自动配置类
     Class<?>[] exclude() default {};
     // for example
     // 移除 Auto-configuration for Spring Data's Redis support.
     // @SpringBootApplication(exclude = {RedisAutoConfiguration.class})
     String[] excludeName() default {};
 }

这又是个复合注解,@AutoConfigurationPackage 和 @Import(AutoConfigurationImportSelector.class)。

@Import 的作用,根据类实现不同接口会有所不同,后面具体情况具体分析

@AutoConfigurationPackage:缓存包路径

先说结论:@AutoConfigurationPackage 会暂存包路径,即 @AutoConfigurationPackage 修饰的类所在的包。

@AutoConfigurationPackage 源码如下:

java 复制代码
 @Import(AutoConfigurationPackages.Registrar.class)
 public @interface AutoConfigurationPackage {
 ​
    String[] basePackages() default {};
 ​
    Class<?>[] basePackageClasses() default {};
 }

在默认情况下,basePackage 为该注解修饰的类的包。

另外,@AutoConfigurationPackage 引入了 AutoConfigurationPackages.Registrar

java 复制代码
 static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
     
         @Override
         public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
             register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
         }
     // 注意 register 方法的第二个参数,这实际上就是你的启动类 MainApplication 所在的包路径。
 ​
         @Override
         public Set<Object> determineImports(AnnotationMetadata metadata) {
             return Collections.singleton(new PackageImports(metadata));
         }
 ​
 }

因为 Registrar 实现了 ImportBeanDefinitionRegistrar ,因此 Spring 会自动调用 registerBeanDefinitions 方法。这部分的源码如下:

java 复制代码
 if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
    // Candidate class is an ImportBeanDefinitionRegistrar ->
    // delegate to it to register additional bean definitions
     // 反射 加载 class 对象
    Class<?> candidateClass = candidate.loadClass();
     // 创建 registrar 实例对象 并回调 Aware 接口
    ImportBeanDefinitionRegistrar registrar =
          ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                this.environment, this.resourceLoader, this.registry);
     // addImportBeanDefinitionRegistrar
    configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
 }

ConfigurationClass 有这么一个集合 LinkedHashMap:

java 复制代码
 private final Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> importBeanDefinitionRegistrars =
       new LinkedHashMap<>();

addImportBeanDefinitionRegistrar 就是把这个 registrar 放入这个 map 中。

registrar 处理时机

而真正调用 registrar 是在 loadBeanDefinitions 时:

方法是这样的:

java 复制代码
 private void loadBeanDefinitionsForConfigurationClass(
       ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
 ​
    // ...
    // 处理 @Bean
    for (BeanMethod beanMethod : configClass.getBeanMethods()) {
       loadBeanDefinitionsForBeanMethod(beanMethod);
    }
    // 处理 ImportedResources
    loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    // 处理 Registrars
    loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
 }

我们关注 loadBeanDefinitionsFromRegistrars 方法

java 复制代码
 private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
    registrars.forEach((registrar, metadata) ->
          registrar.registerBeanDefinitions(metadata, this.registry, this.importBeanNameGenerator));
 }

回调了 Registrar 的 registerBeanDefinitions 方法

java 复制代码
 public static void register(BeanDefinitionRegistry registry, String... packageNames) {
    if (registry.containsBeanDefinition(BEAN)) {
       BasePackagesBeanDefinition beanDefinition = (BasePackagesBeanDefinition) registry.getBeanDefinition(BEAN);
       beanDefinition.addBasePackages(packageNames);
    }
    else {
       registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
    }
 }

这里的 BEAN 是 org.springframework.boot.autoconfigure.AutoConfigurationPackages,执行 registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames)); 方法

java 复制代码
 if (hasBeanCreationStarted()) {
    // Cannot modify startup-time collection elements anymore (for stable iteration)
    synchronized (this.beanDefinitionMap) {
       this.beanDefinitionMap.put(beanName, beanDefinition);
       List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
       updatedDefinitions.addAll(this.beanDefinitionNames);
       updatedDefinitions.add(beanName);
       this.beanDefinitionNames = updatedDefinitions;
       removeManualSingletonName(beanName);
    }
 }

最终向 BeanDefinationMap 注入了 AutoConfigurationPackages,唯一的作用是保存 basePackages 属性。

那费劲心思注入这么个 BeanDefination 干啥呢?源码上的注释是这样的:

Class for storing auto-configuration packages for reference later (e.g. by JPA entity scanner)

即保存 auto-configuration packages,使得之后可能需要获得。有些 jar 包可能需要获取用户代码来执行某些操作,于是就需要依赖这个 BeanDefination。

一处使用是 JacksonAutoConfiguration:

java 复制代码
 @Bean
 public JsonMixinModule jsonMixinModule(ApplicationContext context) {
    List<String> packages = AutoConfigurationPackages.has(context) ? AutoConfigurationPackages.get(context)
          : Collections.emptyList();
    return new JsonMixinModule(context, packages);
 }

为什么要借助 Registrar

ImportBeanDefinitionRegistrar 的 javadoc 是这样写的:

Interface to be implemented by types that register additional bean definitions when processing @Configuration classes.

Useful when operating at the bean definition level (as opposed to @Bean method/instance level) is desired or necessary.

强调了这个接口在 bean definition level 进行操作时非常有用。

如何理解这个 bean definition level 呢?

像 @Bean 注解,我们关注的重点在方法调用后生成的实例,但 ImportBeanDefinitionRegistrar 不同,它关注的是 BeanDefination 而非实例对象,或者说实例对象根本不重要。

因此,Spring 只是使用了 AutoConfigurationPackages 来保存 auto-configuration packages,仅此而已。

AutoConfigurationImportSelector:导入自动配置类

现在我们回到 @EnableAutoConfiguration 的源码,还用 @Import 注入了 AutoConfigurationImportSelector

java 复制代码
 @Import(AutoConfigurationImportSelector.class)

我们发现 AutoConfigurationImportSelector 实现了 DeferredImportSelector 接口。

而 DeferredImportSelector 的 Javadoc 是这么说的:
A variation of ImportSelector that runs after all @Configuration beans have been processed. This type of selector can be particularly useful when the selected imports are @Conditional.

Implementations may also provide an import group which can provide additional sorting and filtering logic across different selectors.

即在处理 AutoConfigurationImportSelector 时,所有其它 @Configuration 已经 process 完毕。

关于 DeferredImportSelector 如何保证在处理时其它 @Configuration 已经 process 完毕,我放在文章最后的附录部分。

并且强调了搭配 @Conditional 注解 particularly useful。我们知道,@Conditional 注解是根据条件来判断是否加载 Bean 的,比如 @ConditionalOnExpression,只有当表达式为 true 的时候才会加载 Bean。所以 DeferredImportSelector + @Conditional 就是实现按需自动配置的关键。

下面我们直接看处理 AutoConfigurationImportSelector 的方法:

java 复制代码
 public void processGroupImports() {
    for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
       Predicate<String> exclusionFilter = grouping.getCandidateFilter();
        // getImports
       grouping.getImports().forEach(entry -> {
          ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
           // processImports 处理 @Import
             processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),exclusionFilter, false);
       });
    }
 }

这里又分为两个部分:

  1. getImports,获取自动配置类
  2. processImports,处理自动配置类

获取自动配置类:getImports

getImports 长这样:

java 复制代码
 public Iterable<Group.Entry> getImports() {
    for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
        // 调用 group.process
        // 两个参数 启动类 MainApplication 和 AutoConfigurationImportSelector
       this.group.process(deferredImport.getConfigurationClass().getMetadata(),
             deferredImport.getImportSelector());
    }
     // 调用 group.selectImports
    return this.group.selectImports();
 }

因为 AutoConfigurationImportSelector 内部类 AutoConfigurationGroup 实现了 DeferredImportSelector.Group,因此有别于默认的情况,group.process 长这样:

java 复制代码
 public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
     
     // getAutoConfigurationEntry
    AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
          .getAutoConfigurationEntry(annotationMetadata);
    this.autoConfigurationEntries.add(autoConfigurationEntry);
    for (String importClassName : autoConfigurationEntry.getConfigurations()) {
       this.entries.putIfAbsent(importClassName, annotationMetadata);
    }
 }

可以看到这里就拿到了 AutoConfigurationEntry,并且包含了自动配置类:

而 getAutoConfigurationEntry 方法就会返回所有应该被导入的自动配置类 auto-configurations。

java 复制代码
 protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
     // 拿到 configurations 集合
    List<String> configurations = 
        getCandidateConfigurations(annotationMetadata, attributes);
    // 省略 过滤 事件传播机制等
    return new AutoConfigurationEntry(configurations, exclusions);
 }

通过 getCandidateConfigurations 方法获取 configurations 集合并进行过滤,继续看 getCandidateConfigurations 方法:

java 复制代码
     protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
         List<String> configurations = new ArrayList<>(
         // 1. loadFactoryNames 
         // 处理 META-INF/spring.factories
             // 第一个参数 EnableAutoConfiguration.class
             // 第二个参数 类加载器
         SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(), 
                                                getBeanClassLoader()));
         // 2. load
         // 处理META-INF/spring/.../.AutoConfiguration.imports
         ImportCandidates.load(AutoConfiguration.class, 
                               getBeanClassLoader()).forEach(configurations::add);
         // configurations 为空的情况
         Assert.notEmpty(configurations,
 "No auto configuration classes found in META-INF/spring.factories nor in META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
 + "are using a custom packaging, make sure that file is correct.");
         return configurations;
     }

我特地保留了 Assert ,透露了很多关键信息。可以看到 auto configuration classes 是分为两类的:

  1. META-INF/spring.factories
  2. META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

实际上,loadFactoryNames 专门拿到 META-INF/spring.factories 的自动配置类,而 load 方法专门处理 META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports 的自动配置类。

老版本的 SpringBoot 可能只有 META-INF/spring.factories,即没有这个 load 方法

1、获取 META-INF/spring.factories

spring.factories 文件大概是下面这样:

ini 复制代码
  org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.lun.hello.spring.boot.autoconfigure.HelloAutoConfiguration
 ​
  # mybatis-spring-boot-autoconfigure:
  # Auto Configure
 org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
 org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
 org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

执行完 SpringFactoriesLoader.loadFactoryNames 后,拿到了所有引入的 jar 的自动配置类,所以我们重点关注 loadFactoryNames 方法。

但奇怪的是,loadSpringFactories 命中了缓存并直接返回了。

SpringFactoriesLoader 的这个 cache 是一个 ConcurrentReferenceHashMap。

java 复制代码
 static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();

我们发现,在 SpringApplication 初始化时,就会调用这个方法:

java 复制代码
 private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    // loadFactoryNames 方法
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 反射 实例化
    // 但是返回值为 null 因此在这不会实例化这些类
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
 }

此时再看 loadSpringFactories 方法:

java 复制代码
 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
       // 省略 cache
       result = new HashMap<>();
       // FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"
       // APP ClassLoader 负责加载我们自定义的 bean 和 jar 包
       Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
       while (urls.hasMoreElements()) {
          // 遍历 url
          URL url = urls.nextElement();
          // 每个 url 可以理解成一个 META-INF/spring.factories 文件
          UrlResource resource = new UrlResource(url);
          // 获取文件中的 k-v 键值对
          Properties properties = PropertiesLoaderUtils.loadProperties(resource);
          for (Map.Entry<?, ?> entry : properties.entrySet()) {
             // key
             String factoryTypeName = ((String) entry.getKey()).trim();
             // value value 可以有多个
             String[] factoryImplementationNames =
                   StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
             // 遍历每个 value
             for (String factoryImplementationName : factoryImplementationNames) {
                result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                      .add(factoryImplementationName.trim());
             }
          }
       }
 ​
       // Replace all lists with unmodifiable lists containing unique elements
       // 去重,不可修改
       result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
             .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
       // 放入缓存
       cache.put(classLoader, result);
 ​
    return result;
 }

整个 result(cache) 大概长这样:

我们来看一个例子:

META-INF/spring.factories 长这样:

ini 复制代码
 org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory

最终对应到这样的结果:

也是因此,后面调用 loadSpringFactories 方法都是直接从 cache 里返回:

java 复制代码
 private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    Map<String, List<String>> result = cache.get(classLoader);
    if (result != null) {
       return result;
    }
    // ...
 }

但是会根据 factoryType 做过滤,比如这样调用:

java 复制代码
 List<String> autoConfigurations = new ArrayList<>(
       SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, this.beanClassLoader));
 // 只会返回 xxxAutoConfiguration 自动配置类
 loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());

只会返回 key 为 EnableAutoConfiguration 的类。

总之,通过此处 SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),getBeanClassLoader())); 我们拿到了所有 META-INF/spring.factories 中的自动配置类。

2、获取 AutoConfiguration.imports

再看 load 方法:

java 复制代码
 public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
    // annotation 就是 AutoConfiguration.class
    ClassLoader classLoaderToUse = decideClassloader(classLoader);
    // 拼接 location 即:
    // META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
    String location = String.format(LOCATION, annotation.getName());
    // 找资源路径
    Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
    List<String> autoConfigurations = new ArrayList<>();
    // 读取 xxx.AutoConfiguration.imports 文件并加入 autoConfigurations 集合
    while (urls.hasMoreElements()) {
       URL url = urls.nextElement();
       autoConfigurations.addAll(readAutoConfigurations(url));
    }
    return new ImportCandidates(autoConfigurations);
 }

url 大概长这样:

jar:file:/{本地Maven仓库路径}/org/springframework/boot/spring-boot-autoconfigure/2.7.2/spring-boot-autoconfigure-2.7.2.jar!/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

然后依次读取每一行并放入 autoConfigurations 集合中。

3、收尾工作

最后,将两个方法的结果合并,就是 getAutoConfigurations 方法的返回值,当然 getAutoConfigurationEntry 会进行一些过滤:

java 复制代码
 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
 configurations = removeDuplicates(configurations);
 Set<String> exclusions = getExclusions(annotationMetadata, attributes);
 checkExcludedClasses(configurations, exclusions);
 configurations.removeAll(exclusions);
 configurations = getConfigurationClassFilter().filter(configurations);

此时回到 getImports 方法的最后,会调用 group.selectImports,处理一些 Exclusions 和排序规则。

java 复制代码
public Iterable<Entry> selectImports() {
	// allExclusions
   Set<String> allExclusions = this.autoConfigurationEntries.stream()
.map(AutoConfigurationEntry::getExclusions).flatMap(Collection::stream).collect(Collectors.toSet());
    
    // processedConfigurations
   Set<String> processedConfigurations = this.autoConfigurationEntries.stream()
         .map(AutoConfigurationEntry::getConfigurations).flatMap(Collection::stream)
         .collect(Collectors.toCollection(LinkedHashSet::new));
   processedConfigurations.removeAll(allExclusions);

   // sort and return
   return sortAutoConfigurations(processedConfigurations, getAutoConfigurationMetadata()).stream()
         .map((importClassName) -> new Entry(this.entries.get(importClassName), importClassName))
         .collect(Collectors.toList());
}

至此,整个 getImports 方法分析完毕。

回顾一下,getImports 以后,我们对每个 Entry 调用 processImports:

java 复制代码
   // getImports
  grouping.getImports().forEach(entry -> {
     ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
      // processImports 处理 @Import
        processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),exclusionFilter, false);
  });

处理自动配置类:processImports

这个 processImports 方法,就是处理 @Import 注解的方法,在解析 @Configuration 时就出现过,简单回忆一下:

java 复制代码
 protected final SourceClass doProcessConfigurationClass(
  ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter){
	// ...
    // 3. Process any @Import annotations
    processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

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

而自动配置类通常没有实现 import 的接口,因此一般走下面这段逻辑:

java 复制代码
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
      currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
// processConfigurationClass
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);

而 processConfigurationClass 就是常规解析 @Configuration 的流程了:

java 复制代码
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
   sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
this.configurationClasses.put(configClass, configClass);

那么随着自动配置类被 process ,整个自动装配原理也就讲的差不多了。

附录:DeferredImportSelector 如何保证最后处理

@Configuration 的类会用 doProcessConfigurationClass 方法进行处理:

java 复制代码
// ...

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
 
//  ...
//  @Bean methods
	Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
	for (MethodMetadata methodMetadata : beanMethods) {
  	 configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
	}
}

对 @Import 核心的处理如下:

java 复制代码
for (SourceClass candidate : importCandidates) {
    // 因为 DeferredImportSelector extends ImportSelector
    // 所以实现了 DeferredImportSelector 的类这个 if 也命中
   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);
      Predicate<String> selectorFilter = selector.getExclusionFilter();
      if (selectorFilter != null) {
         exclusionFilter = exclusionFilter.or(selectorFilter);
      }
       // 处理 DeferredImportSelector
      if (selector instanceof DeferredImportSelector) {
         this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
      }
      else {
         String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
         Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
         processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
      }
   }
   else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
      // Candidate class is an ImportBeanDefinitionRegistrar ->
      // delegate to it to register additional bean definitions
      Class<?> candidateClass = candidate.loadClass();
      ImportBeanDefinitionRegistrar registrar =
            ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
                  this.environment, this.resourceLoader, this.registry);
      configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
   }
   else {
      // Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
      // process it as an @Configuration class
      this.importStack.registerImport(
            currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
      processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
   }
}

Deferred 延迟的关键

我们发现执行完这个方法后,@Import 的构造方法是被调用了,但 DeferredImportSelector 的 selectImports 方法还没有被调用。

java 复制代码
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);

而实现延迟处理的关键是:

java 复制代码
this.deferredImportSelectors.add(holder);

会将 DeferredImportSelector 封装成 DeferredImportSelectorHolder,并放入一个 ArrayList 中。

java 复制代码
private List<DeferredImportSelectorHolder> deferredImportSelectors = new ArrayList<>();

而回调的时机在 parse 方法的最后:

java 复制代码
public void parse(Set<BeanDefinitionHolder> configCandidates) {
   for (BeanDefinitionHolder holder : configCandidates) {
       // parse ...
   }
   this.deferredImportSelectorHandler.process();
}

但你会说,处理 @Configuration 的 @ComponentScan 时会递归调用 parse ,这个 process 的时机,是不是每个 @Configuration 类解析完就处理呢?但源码注释明明是所有 @Configuration 。

java 复制代码
// 递归调用 parse 的源码
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());

而递归调用的 parse 是这样的,入参不同,最后也不会有 this.deferredImportSelectorHandler.process()

java 复制代码
protected final void parse(@Nullable String className, String beanName){
   MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
   processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
}

protected final void parse(Class<?> clazz, String beanName){
   processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}

protected final void parse(AnnotationMetadata metadata, String beanName){
   processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}

所以可以保证 process 延迟处理在最后,这就和注释中提到的 runs after all @Configuration beans have been processed 一致了。

相关推荐
m0_7482451730 分钟前
Web第一次作业
java
小码的头发丝、30 分钟前
Java进阶学习笔记|面向对象
java·笔记·学习
m0_5485147734 分钟前
前端Pako.js 压缩解压库 与 Java 的 zlib 压缩与解压 的互通实现
java·前端·javascript
坊钰1 小时前
【Java 数据结构】移除链表元素
java·开发语言·数据结构·学习·链表
chenziang11 小时前
leetcode hot100 LRU缓存
java·开发语言
会说法语的猪1 小时前
springboot实现图片上传、下载功能
java·spring boot·后端
码农老起1 小时前
IntelliJ IDEA 基本使用教程及Spring Boot项目搭建实战
java·ide·intellij-idea
m0_748239831 小时前
基于web的音乐网站(Java+SpringBoot+Mysql)
java·前端·spring boot
时雨h1 小时前
RuoYi-ue前端分离版部署流程
java·开发语言·前端