Spring 启动过程中的 bean 加载

背景

最近在群里看到了这样的两个问题:

  1. 想通过继承 ZuulProxyAutoConfiguration 的方式实现一些自定义处理,但是项目却启动失败,子类代码和报错信息如下:
@Configuration
public class ZuulConfiguration extends ZuulProxyAutoConfiguration {
    public ZuulConfiguration(MeterRegistry meterRegistry) {
        // ......
    }
}
No qualifying bean of type 'com.netflix.zuul.monitoring.CounterFactory' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
ZuulServerAutoConfiguration.ZuulCounterFactoryConfiguration#counterFactory:
   Did not match:
      - @ConditionalOnBean (types: io.micrometer.core.instrument.MeterRegistry; SearchStrategy: all) did not find any beans of tyio.micrometer.core.instrument.MeterRegistry (OnBeanCondition)
ZuulServerAutoConfiguration.ZuulMetricsConfiguration#counterFactory:
   Did not match:
      - @ConditionalOnMissingClass found unwanted class 'io.micrometer.core.instrument.MeterRegistry' (OnClassCondition)
  1. 项目中的 RedissonLock 加锁失败,通过打印日志发现切面 RedissonLockAspect 没有成功加载到 Spring 容器中,切面代码和日志信息如下:
@Aspect
@Component
@ConditionalOnBean(RedissonClient.class)
class RedissonLockAspect implements Ordered {
    private final RedissonClient redissonClient;
    public RedissonLockAspect(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }
    @Around("execution(* ai.advance..*(..)) && @annotation(ai.advance.lock.RedissonLock)")
    Object lock(ProceedingJoinPoint point) throws Throwable {
        // ......
    }
}
RedissonLockAspect:
   Did not match:
      - @ConditionalOnBean (types: org.redisson.api.RedissonClient; SearchStrategy: all) did not find any beans of type org.redisson.api.RedissonClient (OnBeanCondition)

通过日志可以很明显的发现 ZuulServerAutoConfiguration.ZuulCounterFactoryConfiguration 和 RedissonLockAspect 因为不满足注册成 bean 的前置条件,即容器中没有类型为 MeterRegistry 和 RedissonClient 的 bean, 从而导致其本身未能成功加载到 Spring 容器中。但是通过 debug 却发现 registry.beanDefinitionMap 里面是存在 simpleMeterRegistry 和 redissonClient 两个 bean 的,所以猜测是因为 Spring 容器加载 bean 的前后顺序导致的该问题,通过进一步的 debug 容器启动过程也验证了这一猜想。

写这篇文章的目的,是通过分析 Spring 源码总结一下容器加载 bean 的逻辑究竟是什么样的,因为 Spring 启动过程中所做的事情很多,为了避免喧宾夺主,此文章仅着重描述与 bean 加载有关的源码。依赖版本如下:

  • org.springframework.boot:spring-boot-starter-web:2.3.10.RELEASE
  • org.springframework.cloud:spring-cloud-starter-netflix-zuul:2.2.8.RELEASE

源码分析

1. 项目启动类

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

我相信大家对上面这段代码再熟悉不过了,这是我们启动 Spring Boot 项目最常见的方式,我们先来大致看一下这段代码背后究竟发生了什么。

  • SpringApplication 的构造方法
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 通过分析字节码确定应用类型:REACTIVE/SERVLET/NONE
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   // ApplicationContextInitializer 是 Spring 容器刷新之前的回调接口,允许我们在 refresh ApplicationContext 之前做很多的处理
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   // ApplicationListener 是 Spring 事件机制中的监听器,加载监听器以处理启动过程中的各个事件
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // 通过调用栈信息找出 main 方法所在的类,从而确定启动类
   this.mainApplicationClass = deduceMainApplicationClass();
}
  • 再看一下 run 方法所做的事情
public ConfigurableApplicationContext run(String... args) {
   // ......

   // 通过 spring.factories 机制加载 SpringApplicationRunListener 的实现类,用于启动过程中的事件发送
   SpringApplicationRunListeners listeners = getRunListeners(args);
   // 发送事件 ApplicationStartingEvent
   listeners.starting();
   try {
      // 处理启动参数
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 构建运行环境
      // 在该过程中通过监听器完成了 bootstrap.properties, application.yml 等配置文件的加载
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      // 创建容器
      // 会向容器注册 ConfigurationClassPostProcessor 这个极其重要的 bean,它将会承担注册 bean 和完成自动配置的职责,也是这篇文章的重点
      context = createApplicationContext();
      // 准备容器
      // 进行容器刷新之前的准备工作,包括将启动类封装成 bean 注册到容器中
      // 之前我们提到的 ApplicationContextInitializer 回调接口的处理也是在该过程中完成的
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 刷新容器,完成了其余的 bean 注册和自动配置过程
      refreshContext(context);
      // 刷新容器之后的扩展接口,供开发者实现自定义处理,默认为空
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      // 发送事件 ApplicationStartedEvent
      listeners.started(context);
      callRunners(context, applicationArguments);
   }

   // ......
}
  • getSpringFactoriesInstances
    上面的代码中有一个很重要的方法 getSpringFactoriesInstances, 在 Spring 源码中存在着很多类似的方法,他们最终会调用 SpringFactoriesLoader.loadFactoryNames, 其作用是通过 classLoader 读取 META-INF/spring.factories 文件来查找指定接口的实现类。
    META-INF/spring.factories 文件以 key-value 的形式定义了一系列接口与实现类的对应关系。上面的代码正是利用 spring.factories 机制加载了 ApplicationContextInitializer 和 ApplicationListener 的实现类,从而实现在启动过程中的更多功能。

  • getRunListener
    getRunListener 方法再次利用 spring.factories 机制加载了 SpringApplicationRunListener 的实现类 EventPublishingRunListener。该类是 Spring 中第一个监听器,其内部封装了 ApplicationEventMulticaster 事件广播器。
    EventPublishingRunListener 在收到事件后通过其内部的广播器将事件传递下去,交给 ApplicationEventMulticaster 内部封装的各个 ApplicationListener 去执行各自的逻辑。比如 BootstrapApplicationListener 在监听到 ApplicationEnvironmentPreparedEvent 事件后会初始化 parent 上下文并加载 bootstrap.properties 文件;ConfigFileApplicationListener 在监听到 ApplicationEnvironmentPreparedEvent 事件后,会读取 application.yml/application.properties 等文件以加载配置信息。

  • createApplicationContext
    createApplicationContext 方法会通过 webApplicationType=SERVLET 确定 ApplicationContext 的具体类型为 AnnotationConfigServletWebServerApplicationContext,然后再实例化该类。调用的构造方法如下:

public AnnotationConfigServletWebServerApplicationContext() {
   this.reader = new AnnotatedBeanDefinitionReader(this);
   this.scanner = new ClassPathBeanDefinitionScanner(this);
}

跟进 AnnotatedBeanDefinitionReader 的构造方法最终会调用 AnnotationConfigUtils.registerAnnotationConfigProcessors 方法,这个方法会将一些 processor 注册到 Spring 容器中, 部分代码如下:

public static Set registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {
   // ......
   Set beanDefs = new LinkedHashSet<>(8);
   if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
      RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
      def.setSource(source);
      beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
   }
   // ......
}

可以看到 ConfigurationClassPostProcessor 便是在 createApplicationContext 过程中注册到 Spring 容器中的,该类也是 Spring 完成自动配置的核心,后面会着重介绍。

  • refreshContext
    先来看下 refreshContext 方法后面的调用栈:


    refreshContext.png

    这里最终将所有的 BeanFactoryPostProcessor 委托给了 PostProcessorRegistrationDelegate 类,由其调用 BeanDefinitionRegistryPostProcessor 的 postProcessBeanDefinitionRegistry 和 postProcessBeanFactory 方法,以及 BeanFactoryPostProcessor 的 postProcessBeanFactory 方法,完成所有的后置处理逻辑,其中就包括了完成 bean 注册和自动配置的 ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry 方法。

2. ConfigurationClassPostProcessor

  • postProcessBeanDefinitionRegistry
    我总结了一下 postProcessBeanDefinitionRegistry 后面的方法调用示意图,看下整个流程经过是什么样的,也便于后面的代码介绍:


    refreshContext.png
  • processConfigBeanDefinitions
    先来看一下 processConfigBeanDefinitions 的主要逻辑:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
   // 找出已经注册到容器的 configuration, 也就是启动类
   List 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));
      }
   }

   // ......

   // 创建 ConfigurationClassParser
   ConfigurationClassParser parser = new ConfigurationClassParser(
         this.metadataReaderFactory, this.problemReporter, this.environment,
         this.resourceLoader, this.componentScanBeanNameGenerator, registry);
   do {
      // parse configuration 过程
      // 扫面项目中定义的以及引入的依赖包中的 configuration class, 将其保存到 parser.configurationClasses 中
      parser.parse(candidates);

      // ......

      // register bean 过程
      // 遍历 parser.configurationClasses 完成 bean 的注册,即添加到 registry.beanDefinitionMap 中
      this.reader.loadBeanDefinitions(configClasses);

      // ......
   }while (!candidates.isEmpty());
   // ......
}
  1. 在上面的代码注释中反复提到了一个数据结构 parser.configurationClasses, 这是在 PARSE_CONFIGURATION 过程中用于存储 configuration class 的,其类型是有序的 LinkedHashMap, 所以在后面的 REGISTER_BEAN 过程中遍历是按照插入顺序一一处理 configuration class 的,这也就近似代表了 bean 注册的顺序,下面我们会重点关注 parser.configurationClasses 的插入顺序。
  2. 提到的另外一个数据结构是 registry.beanDefinitionMap, 这是我们常说的容器,bean 注册其实就是将 bean 存储到该 Map 中。
  3. 上面的代码被简单的分为 PARSE_CONFIGURATION 和 REGISTER_BEAN 两个阶段,后面会解释在不同阶段 @ConditionalOnBean/@ConditionOnClass 等注解的不同处理。

3. ConfigurationClassParser

  • 跟进 ConfigurationClassParser.parse 方法会到 ConfigurationClassParser.processConfigurationClass 方法,主要逻辑如下:
protected void processConfigurationClass(ConfigurationClass configClass, Predicate filter) throws IOException {
   // 校验是否需要跳过该类
   if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
      return;
   }
   
   // ......

   // 此处循环是为了处理该类及其父类
   SourceClass sourceClass = asSourceClass(configClass, filter);
   do {
      sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
   }
   while (sourceClass != null);
   // configurationClasses 的定义 Map configurationClasses = new LinkedHashMap<>()
   this.configurationClasses.put(configClass, configClass);
}
  • ConditionEvaluator.shouldSkip 负责判断是否需要跳过该类,其中逻辑如下:
public boolean shouldSkip(@Nullable AnnotatedTypeMetadata metadata, @Nullable ConfigurationPhase phase) {
   // 先会判断该类是否被 Conditional 注解,如果没有此注解那肯定不需要跳过
   if (metadata == null || !metadata.isAnnotated(Conditional.class.getName())) {
      return false;
   }

   // ......

   // 如果有该注解则会对注解条件进行校验,校验包括两部分:
   // 一是注解所需的 ConfigurationPhase 与目前正在进行的阶段是否一致
   // 二是所需条件是否满足
   for (Condition condition : conditions) {
      ConfigurationPhase requiredPhase = null;
      if (condition instanceof ConfigurationCondition) {
         requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
      }
      if ((requiredPhase == null || requiredPhase == phase) && !condition.matches(this.context, metadata)) {
         return true;
      }
   }
   return false;
}

我们先来看一下 ConditionalOnClass 的源码

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {
   // ......
}

再跟进 OnClassCondition 类发现并没有定义 ConfigurationPhase,根据 shouldSkip 方法的逻辑,如果 requiredPhase == null 是要做条件检验的,所以该注解在任何阶段都是生效的。再来看一下 ConditionalOnBean 的源码

@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnBeanCondition.class)
public @interface ConditionalOnBean {
   // ......
}

再跟进 OnBeanCondition 类

@Order(Ordered.LOWEST_PRECEDENCE)
class OnBeanCondition extends FilteringSpringBootCondition implements ConfigurationCondition {
   @Override
   public ConfigurationPhase getConfigurationPhase() {
      return ConfigurationPhase.REGISTER_BEAN;
   }
   // ......
}

可以看到 OnBeanCondition 的 ConfigurationPhase 是 REGISTER_BEAN,也就是说在注册 bean 的阶段该注解才生效。而 ConfigurationClassParser.parse 所处的是 PARSE_CONFIGURATION 阶段,因为阶段不同所以可以忽略该条件。既然 ConditionalOnBean 到目前为止是不生效的,那文章开头的两个问题的答案显然不在这里了,我们先往后看。

  • doProcessConfigurationClass
    接下来是 ConfigurationClassParser 的核心方法 doProcessConfigurationClass,该方法的代码逻辑涉及到递归调用,为了更好的解释,我们借助一下实际场景来模拟下该方法的执行过程。假设项目中存在以下的类文件:
@Configuration
public class MyOuter {
    @Configuration
    class MyInnerComponent {
    }
    @Configuration
    static class MyInnerStaticComponent {
    }
}
@Configuration
public class UserConfiguration {
    @Bean
    public User initUser() {
    }
}
@Configuration
public class ZuulConfiguration extends ZuulProxyAutoConfiguration {
}
@EnableZuulProxy
@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class, args);
    }
}

可以结合着上面的方法调用示意图来理解下面的步骤:

  1. 在第一次调用 configurationClassParser.doProcessConfigurationClass 方法时 configClass 是项目启动类 MainApplication, 因为 @SpringBootApplication 注解的存在,启动类上是存在 @ComponentScan 注解的,所以会触发 processComponentScan 逻辑。
  2. 在处理 @ComponentScan 的逻辑中,先会调用 componentScanParser.parse 方法,该方法通过 classPathBeanDefinitionScanner.doScan 方法扫描所有 basePackage 下的 class 文件。在该过程中会扫描每一级目录及其子目录,将同一级目录下的文件和文件夹按照字母升序排列,然后通过 excludeFilter 和 includeFilter 对 class 文件进行过滤,仅保留 independency configuration class, 即 top-class 和 static-inner-class. 在该模拟场景下,会返回 {MyOuter$MyInnerStaticComponent.class, MyOuter.class, UserConfiguration.class, ZuulConfiguration.class} 这样的列表。
  3. componentScanParser.parse 方法会将得到的 independency configuration class 注册到 Spring 容器中,即添加到 registry.beanDefinitionMap 中,然后按顺序返回 bean 列表。
  4. configurationClassParser.doProcessConfigurationClass 方法会遍历 componentScanParser.parse 返回的 bean 列表并递归调用 configurationClassParser.processConfigurationClass 方法,递归调用过程是下面的步骤 5-8。
  5. 第一个要处理的是 MyOuter$MyInnerStaticComponent.class, 因为该类极其简单,不再赘述过程,最后会直接将其保存到 parser.configurationClasses 中。
  6. 第二个是 MyOuter.class, 因为该类含有内部类,所以会触发 processMemberClass 逻辑,在 processMemberClass 中会先拿到未处理过的内部类,即 MyOuter$MyInnerComponent.class, 递归调用 configurationClassParser.processConfigurationClass 方法,最终将内部类保存到 parser.configurationClasses 中。在递归处理完 MyOuter 的内部类之后,最后处理 MyOuter 将其保存到 parser.configurationClasses 中。
  7. 第三个是 UserConfiguration.class, 该类存在 @Bean method, 所以会先将 initUser method 保存在 UserConfiguration 的 beanMethods 属性中,最后将 UserConfiguration 保存到 parser.configurationClasses 中。
  8. 第四个是 ZuulConfiguration.class, 因为该类存在非 Object 的父类,所以会通过 configurationClassParser.processConfigurationClass 方法中的 do-while 循环处理其父类 ZuulProxyAutoConfiguration 和 ZuulServerAutoConfiguration, 以每一个父类作为 sourceClass 重复 configurationClassParser.doProcessConfigurationClass 的处理逻辑(configClass 始终不变,仍然为子类 ZuulConfiguration),然后将父类的内部类添加到 parser.configurationClasses 中,将父类的 @Bean method 保存到子类 ZuulConfiguration 的 beanMethods 属性中,最后将 ZuulConfiguration 保存到 parser.configurationClasses 中。
  9. 到目前为止,configurationClasses 里面的数据为
[
    "MyOuter$MyInnerStaticComponent.class",
    "MyOuter$MyInnerComponent.class",
    "MyOuter.class",
    "UserConfiguration.class(包含 beanMethods)",
    "ZuulProxyAutoConfiguration$EndpointConfiguration.class",
    "RibbonCommandFactoryConfiguration$HttpClientRibbonConfiguration.class",
    "HttpClientConfiguration$ApacheHttpClientConfiguration.class",
    "HttpClientConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulFilterConfiguration.class",
    "ZuulConfiguration.class(包含 beanMethods)"
]
  1. 回到步骤 4, 在处理完 @ComponentScan 扫描到的类文件之后,继续处理 MainApplication.class, 因为 @EnableZuulProxy 和 @SpringBootApplication 注解的存在会触发 @Import 的处理逻辑。processImports 方法处理逻辑如下:
  • 如果导入的是 ImportSelector 子类, 则将其保存到 parser.deferredImportSelectorHandler.deferredImportSelectors 中,供后面的自动配置过程使用,下一小节会介绍如何使用该数据。然后递归调用 processImports 方法继续以同样的逻辑处理该 ImportSelector 子类, 看看该 ImportSelector 子类上是否还有新的 ImportSelector. 比如在启动类上添加 @EnableZuulProxy 注解会将 EnableCircuitBreakerImportSelector 引入;
  • 如果导入的是 ImportBeanDefinitionRegistrar, 则会将其保存在 configClass 的 importBeanDefinitionRegistrars 属性中,比如在启动类上添加 @MapperScan 注解,会将 MapperScannerRegistrar 引入到启动类;
  • 如果导入的是其他类,则调用 processConfigurationClass 方法当作配置类递归处理,还是以 @EnableZuulProxy 注解为例,该注解会引入 ZuulProxyMarkerConfiguration 并且交给 configurationClassParser 处理,重复上面的步骤。
  1. 完成 MainApplication 的处理之后,parser.configurationClasses 数据如下
[
    "MyOuter$MyInnerStaticComponent.class",
    "MyOuter$MyInnerComponent.class",
    "MyOuter.class",
    "UserConfiguration.class(包含 beanMethods)",
    "ZuulProxyAutoConfiguration$EndpointConfiguration.class",
    "RibbonCommandFactoryConfiguration$HttpClientRibbonConfiguration.class",
    "HttpClientConfiguration$ApacheHttpClientConfiguration.class",
    "HttpClientConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulFilterConfiguration.class",
    "ZuulConfiguration.class(包含 beanMethods)",
    "ZuulProxyMarkerConfiguration(包含 beanMethods)",
    "MainApplication"
]

以 MainApplication 为 configClass 参数调用的 ConfigurationClassParser.processConfigurationClass 方法完全结束后,Spring 就扫描完了 basePackages 指定的配置类及其父类,并且保存到了 parser.configurationClasses 中,对于 top-class 和 static-inner-class 则注册到了 registry.beanDefinitionMap 中,这也就完成了方法调用示意图中的“第一步”。回到 ConfigurationClassParser.parse 方法,该方法的第二步是调用DeferredImportSelectorHandler.process() 方法完成自动配置过程。

4. DeferredImportSelectorHandler

DeferredImportSelectorHandler 是 ConfigurationClassParser 的内部类,负责处理 DeferredImportSelector. 还记得上面提到的 @EnableZuulProxy 注解引入的 EnableCircuitBreakerImportSelector 和 @SpringBootApplication 注解引入的 AutoConfigurationImportSelector 吗,它们都是 DeferredImportSelector 的子类,都被保存在了 DeferredImportSelectorHandler 的 deferredImportSelectors 列表里,然后通过其 process 方法对 selectors 进行处理。

  • process
public void process() {
   List deferredImports = this.deferredImportSelectors;
   this.deferredImportSelectors = null;
   try {
      if (deferredImports != null) {
         DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
         // 对 deferredImportSelectors 按照优先级排序 (比较各个 selector 类上的 @Order)
         deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
         // 对 deferredImportSelectors 分组并注册到 DeferredImportSelectorGroupingHandler
         deferredImports.forEach(handler::register);
         // 由 DeferredImportSelectorGroupingHandler 完成真正的 import 过程
         handler.processGroupImports();
      }
   }
   finally {
      this.deferredImportSelectors = new ArrayList<>();
   }
}

上面代码的注释提到了对 deferredImportSelectors 进行分组,组是以 DeferredImportSelector.getImportGroup() 的不同返回值来划分的,DeferredImportSelector 的不同子类的该方法会返回不同值。
对于子类 AutoConfigurationImportSelector 该方法会返回 AutoConfigurationGroup.class, 那这个组的名字就是 AutoConfigurationGroup.class。
而 EnableCircuitBreakerImportSelector.getImportGroup() 会返回 NULL,那也就意味着它不知道自己是哪个组的,所以会默认分配到 DefaultDeferredImportSelectorGroup.class 小组内。

每个小组的名字和成员列表用 DeferredImportSelectorGrouping 类来保存,该类的数据结构如下:

private static class DeferredImportSelectorGrouping {
   // 组名
   private final DeferredImportSelector.Group group;
   // 组内的 selector 列表
   private final List deferredImports = new ArrayList<>();
}

于是就有了两个小组:

 {AutoConfigurationGroup.class, [AutoConfigurationImportSelector]}
 {DefaultDeferredImportSelectorGroup.class, [EnableCircuitBreakerImportSelector]}
  • processGroupImports
    看方法名可以猜到该方法是负责导入每个 selector 选中的类的,那么 selector 是如何实现自己的目的,将自己选中的类导入呢?来看下代码:
public void processGroupImports() {
   for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
      Predicate exclusionFilter = grouping.getCandidateFilter();
      grouping.getImports().forEach(entry -> {
         ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
         try {
            processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
                  Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
                  exclusionFilter, false);
         }
         // ......
      });
   }
}

上面的代码会循环逐一处理每个 group, 处理每个 group 的过程可以分为两个阶段:getImports 和 processImports. processImports 方法的逻辑在前文中的模拟场景步骤 10 已经介绍过了,主要目的是将 configuration class 存到 parser.configurationClasses 中。这里说下 DeferredImportSelectorGrouping.getImports 方法:

public Iterable getImports() {
   for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
      this.group.process(deferredImport.getConfigurationClass().getMetadata(),
            deferredImport.getImportSelector());
   }
   return this.group.selectImports();
}

这里会调用每个 group 的 process 方法来导入组内的 selector 选中的类,我们以最常见也是 Spring 体系中最重要的 AutoConfigurationGroup 为例,来看下这个导入过程是怎么样的。

  • AutoConfigurationGroup.process
public void process(AnnotationMetadata annotationMetadata, DeferredImportSelector deferredImportSelector) {
   // ......
   // 获取 autoConfigurations
   AutoConfigurationEntry autoConfigurationEntry = ((AutoConfigurationImportSelector) deferredImportSelector)
         .getAutoConfigurationEntry(annotationMetadata);
   this.autoConfigurationEntries.add(autoConfigurationEntry);
   for (String importClassName : autoConfigurationEntry.getConfigurations()) {
      this.entries.putIfAbsent(importClassName, annotationMetadata);
   }
}
  • AutoConfigurationImportSelector.getAutoConfigurationEntry
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
   if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
   }
   AnnotationAttributes attributes = getAttributes(annotationMetadata);
   // 获取 configurations
   List configurations = getCandidateConfigurations(annotationMetadata, attributes);
   // ......
}
  • AutoConfigurationImportSelector.getCandidateConfigurations
protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
   // 利用 spring.factories 机制查找 EnableAutoConfiguration.class 的实现类
   List configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
         getBeanClassLoader());
   Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
         + "are using a custom packaging, make sure that file is correct.");
   return configurations;
}
  • AutoConfigurationImportSelector.getSpringFactoriesLoaderFactoryClass
protected Class getSpringFactoriesLoaderFactoryClass() {
   return EnableAutoConfiguration.class;
}

这里调用栈虽然有点深,但是逻辑很简单,最终又是通过 spring.factories 机制来导入某些接口的实现类,对于 AutoConfigurationImportSelector 则是导入 EnableAutoConfiguration.class 的实现类。

导入完成之后最后通过 AutoConfigurationGroup.selectImports 方法对 autoConfigurations 进行排序,排序逻辑是通过 AutoConfigurationSorter.getInPriorityOrder 方法来完成的。

List getInPriorityOrder(Collection classNames) {
   AutoConfigurationClasses classes = new AutoConfigurationClasses(this.metadataReaderFactory,
         this.autoConfigurationMetadata, classNames);
   List orderedClassNames = new ArrayList<>(classNames);
   // Initially sort alphabetically
   Collections.sort(orderedClassNames);
   // Then sort by order
   orderedClassNames.sort((o1, o2) -> {
      int i1 = classes.get(o1).getOrder();
      int i2 = classes.get(o2).getOrder();
      return Integer.compare(i1, i2);
   });
   // Then respect @AutoConfigureBefore @AutoConfigureAfter
   orderedClassNames = sortByAnnotation(classes, orderedClassNames);
   return orderedClassNames;
}
  • 排序规则有三个,最优规则是 @AutoConfigureBefore @AutoConfigureAfter 的限制条件,其次是 @Order 优先级,最低级规则是字母排序。最后将排好序的 configurations 存储到 parser.configurationClasses 中。这时候我们模拟场景的 parser.configurationClasses 应该是这样子的:
[
    "MyOuter$MyInnerStaticComponent.class",
    "MyOuter$MyInnerComponent.class",
    "MyOuter.class",
    "UserConfiguration.class(包含 beanMethods)",
    "ZuulProxyAutoConfiguration$EndpointConfiguration.class",
    "RibbonCommandFactoryConfiguration$HttpClientRibbonConfiguration.class",
    "HttpClientConfiguration$ApacheHttpClientConfiguration.class",
    "HttpClientConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulMetricsConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class",
    "ZuulServerAutoConfiguration$ZuulFilterConfiguration.class",
    "ZuulConfiguration.class(包含 beanMethods)",
    "ZuulProxyMarkerConfiguration(包含 beanMethods)",
    "MainApplication",
    sortedAutoConfigurations......
]

5. ConfigurationClassBeanDefinitionReader

回到 ConfigurationClassPostProcessor.processConfigBeanDefinitions 方法,在完成ConfigurationClassParser.parse 方法的调用后,接下来将进入 ConfigurationClassBeanDefinitionReader.loadBeanDefinitions 方法

  • loadBeanDefinitions
public void loadBeanDefinitions(Set configurationModel) {
   TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();
   // 顺序遍历所有的 configurationClasses, 逐一处理
   for (ConfigurationClass configClass : configurationModel) {
      loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);
   }
}

还记得我之前说 parser.configurationClasses 的类型是 LinkedHashMap 吗,这里的 configurationModel 正是由 parser.configurationClasses 转化生成的 LinkedHashSet, 所以这里的循环是按照之前的 parser.configurationClasses 的插入顺序处理的。

  • loadBeanDefinitionsForConfigurationClass
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
   // 调用内部类的 TrackedConditionEvaluator.shouldSkip 方法来判断是否需要跳过该 configuration
   if (trackedConditionEvaluator.shouldSkip(configClass)) {
      String beanName = configClass.getBeanName();
      // 如果需要跳过该类,但是该类又已经注册到 Spring 容器中,则从容器中移除
      if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {
         this.registry.removeBeanDefinition(beanName);
      }
      this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());
      return;
   }
   // 正常处理该类以及 @Bean method, 加载到 Spring 容器中
}
  • shouldSkip
public boolean shouldSkip(ConfigurationClass configClass) {
   Boolean skip = this.skipped.get(configClass);
   if (skip == null) {
      if (configClass.isImported()) {
         boolean allSkipped = true;
         for (ConfigurationClass importedBy : configClass.getImportedBy()) {
            if (!shouldSkip(importedBy)) {
               allSkipped = false;
               break;
            }
         }
         if (allSkipped) {
            // The config classes that imported this one were all skipped, therefore we are skipped...
            skip = true;
         }
      }
      if (skip == null) {
         // 注意这里是 ConfigurationPhase.REGISTER_BEAN
         skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
      }
      this.skipped.put(configClass, skip);
   }
   return skip;
}

注意这里在调用 conditionEvaluator.shouldSkip 方法时 phase = ConfigurationPhase.REGISTER_BEAN, 也就是说 @ConditionalOnBean 等注解在该阶段生效了。那么文章开头的两个问题的答案也在这里了。

  • 问题一
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(MeterRegistry.class)
protected static class ZuulCounterFactoryConfiguration {
   @Bean
   @ConditionalOnBean(MeterRegistry.class)
   @ConditionalOnMissingBean(CounterFactory.class)
   public CounterFactory counterFactory(MeterRegistry meterRegistry) {
      return new DefaultCounterFactory(meterRegistry);
   }
}
  1. 因为 autoConfigurations 在待处理的配置类列表 configurationModel 里面的位置比较靠后,所以在处理 ZuulServerAutoConfiguration$ZuulCounterFactoryConfiguration.class 时通过 autoConfiguration 装配的 SimpleMeterRegistry bean 还不存在。那么其内部的 @Bean method counterFactory 因为不满足条件 @ConditionalOnBean(MeterRegistry.class) 所以被跳过了,未能注册到 Spring 容器中。即使后来 SimpleMeterRegistry 被加载进来了,也于事无补了,失去的就是失去了。
  2. 但是如果我们没有增加 ZuulConfiguration 这个类,那么 ZuulProxyAutoConfiguration 会在 DeferredImportSelectorHandler.process() 过程中加载进来,根据字母排序规则其位置一定在 SimpleMetricsExportAutoConfiguration 之后,所以其父类 ZuulServerAutoConfiguration 的 @Bean method counterFactory 可以成功注册到 Spring 容器中。
  3. 这里补充一下,ZuulProxyMarkerConfiguration 定义了一个空的内部类 Marker, 一开始完全不知道这个类是如何发光发热的。在 debug 过程中发现该类的作用是作为 ZuulProxyAutoConfiguration 装配的前置条件。因为在启动类加上 @EnableZuulProxy 注解之后,那么会通过 parser.processImports 逻辑提前将 ZuulProxyMarkerConfiguration 以及内部的 @Bean method 注册到 Spring 容器中,也就满足了 ZuulProxyAutoConfiguration 的前置条件 @ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)。相反,如果没有加 @EnableZuulProxy 注解,那么 ZuulProxyAutoConfiguration 会因为没有 ZuulProxyMarkerConfiguration.Marker bean 而不会加载到 Spring 容器中。
  • 问题二
    问题二中的 RedissonLockAspect 是 basePackages 下的 top-class, 所以在 componentScanParser.parse 过程中会被成功注册到 Spring 容器中,即添加到 registry.beanDefinitionMap 中。但是在 loadBeanDefinitionsForConfigurationClass 方法中,因为未能找到 autoConfiguration 导入的 RedissonClient.class 从而导致 shouldSkip = true, 所以又从容器移除了。到后来 RedissonClient.class 加载到容器中之后,一切又是追悔莫及,还是失去了 RedissonLockAspect.

总结

  1. Spring 注册 bean 的过程可以分为两个阶段 PARSE_CONFIGURATION 和 REGISTER_BEAN,条件注解在各个阶段是否生效取决于 CONFIGURATION_PHASE.
  2. 两个重要的数据结构 LinkedHashMap configurationClasses 和 ConcurrentHashMap beanDefinitionMap. 在 PARSE_CONFIGURATION 阶段 ConfigurationClassParser 将配置类存储到 configurationClasses 中,在 REGISTER_BEAN 阶段 ConfigurationClassPostProcessor 顺序遍历 configurationClasses 将其及相关配置类注册到 beanDefinitionMap 中。
  3. basePackages 下的配置类、内部类、beanMethod 及其父类的内部类、父类的 beanMethod 会优先于通过 autoConfiguration 机制引入的配置类被处理。
  4. 文章开头提到的两个问题都是因为 @ComponentScan 指定的 basePackages 下的 configurationClass、内部类、beanMethod、父类的内部类、父类的 beanMethod 依赖了通过 autoConfiguration 装配的 bean, 从而导致在 REGISTER_BEAN 过程中因为不满足条件而未能注册到 Spring 容器中。
    Spring 启动过程中所做的事情极其繁多,除了文章中提到的,还有 bean 的实例化、beanPostProcessor 的应用等等,因为篇幅有限所以有些内容只是一带而过。本文目的在于通过两个小问题给大家梳理下 bean 的加载顺序,如果有不准确的地方欢迎指正。

你可能感兴趣的:(Spring 启动过程中的 bean 加载)