Spring 3.0 提供 Configuration、@Bean 等注解,进一步支持 JavaConfig 编程方式。本文就 Configuration 的工作原理进行分析(Spring-5.2.2)。启动注解驱动时,Spring 会自动注入 ConfigurationClassPostProcessor 后置处理器来处理 Configuration 注解。

1. 工作原理

Configuration 注解的核心处理器是 ConfigurationClassPostProcessor,这个后置处理器实现了 BeanDefinitionRegistryPostProcessor 接口。这种后置处理器的功能,一般都是在容器启动时向容器中注入一些 BeanDefinition,比如 Mybatis 的 MapperScan 注解也是通过注册 MapperScannerConfigurer 后置处理器来处理 @Mapper 注解。

1.1 工作流程

Spring 注解驱动(06)@Configuration - 图1说明:ConfigurationClassPostProcessor 是容器启动时 AbstractApplicationContext#invokeBeanFactoryPostProcessors 阶段执行的。它会解析配置类中的注解信息,将解析后的 BeanDefinition 注册到容器中。ConfigurationClassPostProcessor 功能分为两部分:

  1. 核心逻辑:postProcessBeanDefinitionRegistry 调用 processConfigBeanDefinitions 方法解析配置类。
    • 解析配置类:将配置类 ConfigurationClass 的解析委托给 ConfigurationClassParser,解析后的结果都保存在 ConfigurationClass 中。
    • 加载到容器:ConfigurationClassBeanDefinitionReader 将解析后的结果 ConfigurationClass 加载到容器中。
    • 循环解析:如果新加载的类中包含新的配置类,递归解析直到没有新的配置类。
  2. 功能优化:postProcessBeanFactory 方法主要是对配置类进行字节码提升。

    • 配置类字节码提升:ConfigurationClassEnhancer 代理 Configuration 标注的配置类,解决直接调用 @Bean 标注的方法的场景下,每次都生成一个新实例的 Bug。这也是 Configuration 和 @Compoent 的主要区别。
    • 注入 ImportAwareBeanPostProcessor 后置处理器。

      1.2 配置类区分

      在分析源码前,我们先了解一下配置类区别:
  3. 重量级配置类:标注有 @Configuration 的配置类。在 Spring 5.2 之后,@Configuration 添加了一个新的属性 proxyBeanMethods,当 proxyBeanMethods=false 时也是轻量级配置类。代码见 ConfigurationClassUtils#checkConfigurationClassCandidate。

  4. 轻量级配置类:其它配置类,如 Component、ComponentScan、Import、ImportResource,或者类中标注有 @Bean 的方法。代码见 ConfigurationClassUtils#isConfigurationCandidate。
  5. 二者主要区别:重量级配置类在调用 enhanceConfigurationClasses 方法时,会被 ConfigurationClassEnhancer 字节码提升。主要是解决直接调用配置类 @Bean 方法时,委托给 beanFactory#getBean(xxx) 获取对象,避免重新创建新的对象,破坏单例模式。

    2. 源码分析

    AnnotationConfigUtils#registerAnnotationConfigProcessors 会注册一系的注解驱动的后置处理器,其中就包括
    ConfigurationClassPostProcessor 处理配置类的后置处理器。这几个核心类的功能如下:
  • ConfigurationClassPostProcessor:核心类,用于解析配置类并注册到容器中。
  • ConfigurationClassParser:解析配置类 ConfigurationClass 。分别处理其内部类、接口、父类上的 @PropertySource、@ComponentScan、@Import、@ImportResource、@Bean 注解。解析的结果分别保存在 configurationClasses、importBeanDefinitionRegistrar、importedResources、beanMethods 集合中。
  • ConfigurationClassBeanDefinitionReader:将解析的结果 ConfigurationClass 注册到容器中。
  • ConfigurationClassEnhancer:对配置类进行字节码提升,解决 @Bean 方法直接调用时都生成新的实例问题。

    2.1 ConfigurationClassPostProcessor

    ConfigurationClassPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,在容器启动时首先调用 postProcessBeanDefinitionRegistry 处理配置类,然后调用 postProcessBeanFactory 对重量级配置类进行字节码提升。其中最核心的方法是 postProcessBeanDefinitionRegistry,它会递归解析配置类,并将解析的 BeanDefinition 加载到容器中。

    1. public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
    2. List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
    3. String[] candidateNames = registry.getBeanDefinitionNames();
    4. // 1. 过滤配置类 configCandidates
    5. for (String beanName : candidateNames) {
    6. BeanDefinition beanDef = registry.getBeanDefinition(beanName);
    7. if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
    8. } else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
    9. configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
    10. }
    11. }
    12. // 2. 配置类加载顺序,Spring 都是通过排序解析执行顺序
    13. configCandidates.sort((bd1, bd2) -> {
    14. int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
    15. int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
    16. return Integer.compare(i1, i2);
    17. });
    18. // 3. 递归解析配置类,核心流程
    19. ConfigurationClassParser parser = new ConfigurationClassParser(
    20. this.metadataReaderFactory, this.problemReporter, this.environment,
    21. this.resourceLoader, this.componentScanBeanNameGenerator, registry);
    22. Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
    23. Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
    24. do {
    25. // 3.1 解析配置类
    26. parser.parse(candidates);
    27. parser.validate();
    28. Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
    29. configClasses.removeAll(alreadyParsed);
    30. // 3.2 解析后的 Bean 加载到容器中
    31. if (this.reader == null) {
    32. this.reader = new ConfigurationClassBeanDefinitionReader(
    33. registry, this.sourceExtractor, this.resourceLoader, this.environment,
    34. this.importBeanNameGenerator, parser.getImportRegistry());
    35. }
    36. this.reader.loadBeanDefinitions(configClasses);
    37. alreadyParsed.addAll(configClasses);
    38. // 3.3 如果解析后发现新的配置类,递归解析
    39. candidates.clear();
    40. if (registry.getBeanDefinitionCount() > candidateNames.length) {
    41. String[] newCandidateNames = registry.getBeanDefinitionNames();
    42. Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
    43. Set<String> alreadyParsedClasses = new HashSet<>();
    44. for (ConfigurationClass configurationClass : alreadyParsed) {
    45. alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
    46. }
    47. for (String candidateName : newCandidateNames) {
    48. if (!oldCandidateNames.contains(candidateName)) {
    49. BeanDefinition bd = registry.getBeanDefinition(candidateName);
    50. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
    51. !alreadyParsedClasses.contains(bd.getBeanClassName())) {
    52. candidates.add(new BeanDefinitionHolder(bd, candidateName));
    53. }
    54. }
    55. }
    56. candidateNames = newCandidateNames;
    57. }
    58. }
    59. while (!candidates.isEmpty());
    60. }

    说明:从代码中我们可以看到 ConfigurationClassPostProcessor 主要是解决了配置类的递归解析,最核心的功能配置类解析委托给了 ConfigurationClassParser,而 BeanDefinition 的加载则委托给了 ConfigurationClassBeanDefinitionReader。

    2.2 ConfigurationClassParser

    ConfigurationClassParser 会对配置类进行解析,解析后的结果都存放到 ConfigurationClass 中,最后由 ConfigurationClassBeanDefinitionReader 加载到容器中。ConfigurationClassParser 首先会调用 processConfigurationClass 方法递归处理配置类及其父类,真正的解析由 doProcessConfigurationClass 完成。doProcessConfigurationClass 方法依次解析内部类、@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean、接口、以及父类上的注解。

    1. protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
    2. throws IOException {
    3. // 1. 解析内部类
    4. if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
    5. processMemberClasses(configClass, sourceClass);
    6. }
    7. // 2. 解析 @PropertySource
    8. for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
    9. sourceClass.getMetadata(), PropertySources.class,
    10. org.springframework.context.annotation.PropertySource.class)) {
    11. if (this.environment instanceof ConfigurableEnvironment) {
    12. processPropertySource(propertySource);
    13. }
    14. }
    15. // 3. 解析 @ComponentScan
    16. Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(
    17. sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);
    18. if (!componentScans.isEmpty() &&
    19. !this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
    20. for (AnnotationAttributes componentScan : componentScans) {
    21. // 3.1 委托给 componentScanParser
    22. Set<BeanDefinitionHolder> scannedBeanDefinitions =
    23. this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
    24. for (BeanDefinitionHolder holder : scannedBeanDefinitions) {
    25. BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();
    26. if (bdCand == null) {
    27. bdCand = holder.getBeanDefinition();
    28. }
    29. // 3.2 递归解析配置类
    30. if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {
    31. parse(bdCand.getBeanClassName(), holder.getBeanName());
    32. }
    33. }
    34. }
    35. }
    36. // 4. 解析 @Import
    37. processImports(configClass, sourceClass, getImports(sourceClass), true);
    38. // 5. 解析 @ImportResource
    39. AnnotationAttributes importResource =
    40. AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);
    41. if (importResource != null) {
    42. String[] resources = importResource.getStringArray("locations");
    43. Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");
    44. for (String resource : resources) {
    45. String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);
    46. configClass.addImportedResource(resolvedResource, readerClass);
    47. }
    48. }
    49. // 6. 解析 @Bean
    50. Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
    51. for (MethodMetadata methodMetadata : beanMethods) {
    52. configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
    53. }
    54. // 7. default methods on interfaces
    55. processInterfaces(configClass, sourceClass);
    56. // 8. 解析父类 superclass
    57. if (sourceClass.getMetadata().hasSuperClass()) {
    58. String superclass = sourceClass.getMetadata().getSuperClassName();
    59. if (superclass != null && !superclass.startsWith("java") &&
    60. !this.knownSuperclasses.containsKey(superclass)) {
    61. this.knownSuperclasses.put(superclass, configClass);
    62. return sourceClass.getSuperClass();
    63. }
    64. }
    65. // 9. 没有父类,解析完成
    66. return null;
    67. }

    说明:ConfigurationClassParser 依次处理后,解析的结果如下

  1. 内部类:调用 processConfigurationClass 方法递归解析内部类。
  2. @PropertySource:解析后将资源直接添加到 Environment 环境变量中。
  3. @ComponentScan:如果扫描后的是配置类,则递归调用 processConfigurationClass 方法解析。同时将扫描后的类添加到 configurationClasses** **集合中。
  4. @Import:可以注入 ImportSelector、ImportBeanDefinitionRegistrar、普通类三种情况。其中 ImportSelector 和变通类最终都添加到 configurationClasses 集合中。而 ImportBeanDefinitionRegistrar 则添加到 ConfigurationClass.importBeanDefinitionRegistrars 集合中。
  5. @ImportResource:添加到 ConfigurationClass.importedResources 集合中。
  6. @Bean:添加到 ConfigurationClass.beanMethods 集合中。
  7. 接口:递归遍历接口,处理接口中 @Bean 注解。
  8. 父类:递归遍历父类,调用 processConfigurationClass 方法。

总结:最终 ConfigurationClass 解析后的结果存放到四个集合中:

  1. ConfigurationClassParser.configurationClasses:所有解析的配置类都存放在这个集合中。
  2. ConfigurationClass.importBeanDefinitionRegistrar:通过 @Import 注入的 ImportBeanDefinitionRegistrar 类型。
  3. ConfigurationClass.importedResources:通过 @ImportResource 注入。
  4. ConfigurationClass.beanMethods :通过 @Bean 注入。

    2.3 ConfigurationClassBeanDefinitionReader

    ConfigurationClassBeanDefinitionReader 将解析后的配置注入到 Spring 容器中,对应最终解析后的四个集合中的元素。reader.loadBeanDefinitions 依次遍历所有的 configurationClasses,最终会调用 loadBeanDefinitionsForConfigurationClass 加载解析后的类。

    1. private void loadBeanDefinitionsForConfigurationClass(
    2. ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {
    3. ...
    4. // 1. configurationClasses:配置类本身
    5. if (configClass.isImported()) {
    6. registerBeanDefinitionForImportedConfigurationClass(configClass);
    7. }
    8. // 2. importBeanDefinitionRegistrar:@Import注入ImportBeanDefinitionRegistrar
    9. for (BeanMethod beanMethod : configClass.getBeanMethods()) {
    10. loadBeanDefinitionsForBeanMethod(beanMethod);
    11. }
    12. // 3. importedResources:通过 @ImportResource 注入
    13. loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());
    14. // 4. beanMethods:通过 @Bean 注入
    15. loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
    16. }

    说明:其实到这一步,配置类就基本解析完成,后面只是一些小的优化点。

    2.4 ConfigurationClassEnhancer

    ConfigurationClassEnhancer 对 Configuration 配置类进行字节码提升,那关键是为什么要进行字节码提升呢?如果我们直接调用配置类标注有 @Bean 注解的方法,每调用一次都会生成一个实例。你可能会说我们怎么可能直接这么使用呢?实际上,在使用 Configuration 配置类时,我们经常调用 transactionAttributeSource() 获取另外一个注入的 Bean 对象,需要对 @Bean 标注的方法进行处理。

  5. @Bean 标注的方法需要直接从容器中获取。毫无疑问,如果不做字节码提升,从 beanFactory#getBean() 中获取实例,就会导致单例失效,因此 Spring 对 @Configuration 标注的配置类进行字节码提升。

  6. 同样的情况,还有返回 FactoryBean 的情况。

    1. @Configuration
    2. public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {
    3. @Bean
    4. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    5. public TransactionAttributeSource transactionAttributeSource() {
    6. return new AnnotationTransactionAttributeSource();
    7. }
    8. @Bean
    9. @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    10. public TransactionInterceptor transactionInterceptor() {
    11. TransactionInterceptor interceptor = new TransactionInterceptor();
    12. interceptor.setTransactionAttributeSource(transactionAttributeSource());
    13. if (this.txManager != null) {
    14. interceptor.setTransactionManager(this.txManager);
    15. }
    16. return interceptor;
    17. }
    18. }

    说明:只有 Configuration 标注的配置类才会进行字节码提升。ConfigurationClassEnhancer 提升的关键是 BeanMethodInterceptor 和 BeanFactoryAwareMethodInterceptor 两个处理器,前者处理 @Bean 注解,后者用于设置 BeanFactory 容器。

    1. private static final Callback[] CALLBACKS = new Callback[] {
    2. new BeanMethodInterceptor(),
    3. new BeanFactoryAwareMethodInterceptor(),
    4. NoOp.INSTANCE
    5. };

    说明:只有 Configuration 标注的配置类才会进行字节码提升,这是和普通 Component 的主要区别。如果对 Component 这样轻量级的配置类也进行字节码提升,代价太大,Spring 做了一个折中,只对 Configuration 配置类进行提升。

  7. BeanMethodInterceptor:处理 @Bean 注解。isMatch 方法只会拦截 @Bean 标注的方法,而 intercept 方法会针对方法的返回值是否是 FactoryBean 类型分别处理。这两种情况,本质都是调用 beanFactory#getBean() 方法,从容器中直接获取实例。

    1. 如果返回值是 FactoryBean 类型。调用 enhanceFactoryBean 对这个 FactoryBean 进行字节码提升。
    2. 否则,直接调用 resolveBeanReference 进行处理。
  8. BeanFactoryAwareMethodInterceptor:调用 setFactory(BeanFactory) 设置容器。

    2.5 ImportAwareBeanPostProcessor

    ImportAwareBeanPostProcessor 用于设置对应的配置类信息。ConfigurationClassParser 进行配置类解析时,会将 @Import 对应的关系保存在 ImportStack 中,ImportAwareBeanPostProcessor 则在 bean 初始化时回调。

    1. // AbstractAutowireCapableBeanFactoryinitializeBean 时回调
    2. @Override
    3. public Object postProcessBeforeInitialization(Object bean, String beanName) {
    4. if (bean instanceof ImportAware) {
    5. // ConfigurationClassParser解析时会通过ImportStack保存配置类信息
    6. ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);
    7. AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());
    8. if (importingClass != null) {
    9. ((ImportAware) bean).setImportMetadata(importingClass);
    10. }
    11. }
    12. return bean;
    13. }

    3. 总结时刻

    推荐阅读

  9. ImportSelector 与 DeferredImportSelector 的区别》:Spring Boot 自动装配时,自定义配置为什么能覆盖自动装配的配置类?


每天用心记录一点点。内容也许不重要,但习惯很重要!