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 工作流程
说明:ConfigurationClassPostProcessor 是容器启动时 AbstractApplicationContext#invokeBeanFactoryPostProcessors 阶段执行的。它会解析配置类中的注解信息,将解析后的 BeanDefinition 注册到容器中。ConfigurationClassPostProcessor 功能分为两部分:
- 核心逻辑:postProcessBeanDefinitionRegistry 调用 processConfigBeanDefinitions 方法解析配置类。
- 解析配置类:将配置类 ConfigurationClass 的解析委托给 ConfigurationClassParser,解析后的结果都保存在 ConfigurationClass 中。
- 加载到容器:ConfigurationClassBeanDefinitionReader 将解析后的结果 ConfigurationClass 加载到容器中。
- 循环解析:如果新加载的类中包含新的配置类,递归解析直到没有新的配置类。
功能优化:postProcessBeanFactory 方法主要是对配置类进行字节码提升。
重量级配置类:标注有 @Configuration 的配置类。在 Spring 5.2 之后,@Configuration 添加了一个新的属性 proxyBeanMethods,当 proxyBeanMethods=false 时也是轻量级配置类。代码见 ConfigurationClassUtils#checkConfigurationClassCandidate。
- 轻量级配置类:其它配置类,如 Component、ComponentScan、Import、ImportResource,或者类中标注有 @Bean 的方法。代码见 ConfigurationClassUtils#isConfigurationCandidate。
- 二者主要区别:重量级配置类在调用 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 加载到容器中。
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();// 1. 过滤配置类 configCandidatesfor (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {} else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// 2. 配置类加载顺序,Spring 都是通过排序解析执行顺序configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// 3. 递归解析配置类,核心流程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 {// 3.1 解析配置类parser.parse(candidates);parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// 3.2 解析后的 Bean 加载到容器中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);// 3.3 如果解析后发现新的配置类,递归解析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());}
说明:从代码中我们可以看到 ConfigurationClassPostProcessor 主要是解决了配置类的递归解析,最核心的功能配置类解析委托给了 ConfigurationClassParser,而 BeanDefinition 的加载则委托给了 ConfigurationClassBeanDefinitionReader。
2.2 ConfigurationClassParser
ConfigurationClassParser 会对配置类进行解析,解析后的结果都存放到 ConfigurationClass 中,最后由 ConfigurationClassBeanDefinitionReader 加载到容器中。ConfigurationClassParser 首先会调用 processConfigurationClass 方法递归处理配置类及其父类,真正的解析由 doProcessConfigurationClass 完成。doProcessConfigurationClass 方法依次解析内部类、@PropertySource、@ComponentScan、@Import、@ImportResource、@Bean、接口、以及父类上的注解。
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)throws IOException {// 1. 解析内部类if (configClass.getMetadata().isAnnotated(Component.class.getName())) {processMemberClasses(configClass, sourceClass);}// 2. 解析 @PropertySourcefor (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}}// 3. 解析 @ComponentScanSet<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) {// 3.1 委托给 componentScanParserSet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());for (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}// 3.2 递归解析配置类if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// 4. 解析 @ImportprocessImports(configClass, sourceClass, getImports(sourceClass), true);// 5. 解析 @ImportResourceAnnotationAttributes 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);}}// 6. 解析 @BeanSet<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 7. default methods on interfacesprocessInterfaces(configClass, sourceClass);// 8. 解析父类 superclassif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);return sourceClass.getSuperClass();}}// 9. 没有父类,解析完成return null;}
说明:ConfigurationClassParser 依次处理后,解析的结果如下
- 内部类:调用 processConfigurationClass 方法递归解析内部类。
- @PropertySource:解析后将资源直接添加到 Environment 环境变量中。
- @ComponentScan:如果扫描后的是配置类,则递归调用 processConfigurationClass 方法解析。同时将扫描后的类添加到 configurationClasses** **集合中。
- @Import:可以注入 ImportSelector、ImportBeanDefinitionRegistrar、普通类三种情况。其中 ImportSelector 和变通类最终都添加到 configurationClasses 集合中。而 ImportBeanDefinitionRegistrar 则添加到 ConfigurationClass.importBeanDefinitionRegistrars 集合中。
- @ImportResource:添加到 ConfigurationClass.importedResources 集合中。
- @Bean:添加到 ConfigurationClass.beanMethods 集合中。
- 接口:递归遍历接口,处理接口中 @Bean 注解。
- 父类:递归遍历父类,调用 processConfigurationClass 方法。
总结:最终 ConfigurationClass 解析后的结果存放到四个集合中:
- ConfigurationClassParser.configurationClasses:所有解析的配置类都存放在这个集合中。
- ConfigurationClass.importBeanDefinitionRegistrar:通过 @Import 注入的 ImportBeanDefinitionRegistrar 类型。
- ConfigurationClass.importedResources:通过 @ImportResource 注入。
ConfigurationClass.beanMethods :通过 @Bean 注入。
2.3 ConfigurationClassBeanDefinitionReader
ConfigurationClassBeanDefinitionReader 将解析后的配置注入到 Spring 容器中,对应最终解析后的四个集合中的元素。reader.loadBeanDefinitions 依次遍历所有的 configurationClasses,最终会调用 loadBeanDefinitionsForConfigurationClass 加载解析后的类。
private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {...// 1. configurationClasses:配置类本身if (configClass.isImported()) {registerBeanDefinitionForImportedConfigurationClass(configClass);}// 2. importBeanDefinitionRegistrar:@Import注入ImportBeanDefinitionRegistrarfor (BeanMethod beanMethod : configClass.getBeanMethods()) {loadBeanDefinitionsForBeanMethod(beanMethod);}// 3. importedResources:通过 @ImportResource 注入loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());// 4. beanMethods:通过 @Bean 注入loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());}
说明:其实到这一步,配置类就基本解析完成,后面只是一些小的优化点。
2.4 ConfigurationClassEnhancer
ConfigurationClassEnhancer 对 Configuration 配置类进行字节码提升,那关键是为什么要进行字节码提升呢?如果我们直接调用配置类标注有 @Bean 注解的方法,每调用一次都会生成一个实例。你可能会说我们怎么可能直接这么使用呢?实际上,在使用 Configuration 配置类时,我们经常调用 transactionAttributeSource() 获取另外一个注入的 Bean 对象,需要对 @Bean 标注的方法进行处理。
@Bean 标注的方法需要直接从容器中获取。毫无疑问,如果不做字节码提升,从 beanFactory#getBean() 中获取实例,就会导致单例失效,因此 Spring 对 @Configuration 标注的配置类进行字节码提升。
同样的情况,还有返回 FactoryBean 的情况。
@Configurationpublic class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {return new AnnotationTransactionAttributeSource();}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionInterceptor transactionInterceptor() {TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionAttributeSource(transactionAttributeSource());if (this.txManager != null) {interceptor.setTransactionManager(this.txManager);}return interceptor;}}
说明:只有 Configuration 标注的配置类才会进行字节码提升。ConfigurationClassEnhancer 提升的关键是 BeanMethodInterceptor 和 BeanFactoryAwareMethodInterceptor 两个处理器,前者处理 @Bean 注解,后者用于设置 BeanFactory 容器。
private static final Callback[] CALLBACKS = new Callback[] {new BeanMethodInterceptor(),new BeanFactoryAwareMethodInterceptor(),NoOp.INSTANCE};
说明:只有 Configuration 标注的配置类才会进行字节码提升,这是和普通 Component 的主要区别。如果对 Component 这样轻量级的配置类也进行字节码提升,代价太大,Spring 做了一个折中,只对 Configuration 配置类进行提升。
BeanMethodInterceptor:处理 @Bean 注解。isMatch 方法只会拦截 @Bean 标注的方法,而 intercept 方法会针对方法的返回值是否是 FactoryBean 类型分别处理。这两种情况,本质都是调用 beanFactory#getBean() 方法,从容器中直接获取实例。
- 如果返回值是 FactoryBean 类型。调用 enhanceFactoryBean 对这个 FactoryBean 进行字节码提升。
- 否则,直接调用 resolveBeanReference 进行处理。
BeanFactoryAwareMethodInterceptor:调用 setFactory(BeanFactory) 设置容器。
2.5 ImportAwareBeanPostProcessor
ImportAwareBeanPostProcessor 用于设置对应的配置类信息。ConfigurationClassParser 进行配置类解析时,会将 @Import 对应的关系保存在 ImportStack 中,ImportAwareBeanPostProcessor 则在 bean 初始化时回调。
// AbstractAutowireCapableBeanFactoryinitializeBean 时回调@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {if (bean instanceof ImportAware) {// ConfigurationClassParser解析时会通过ImportStack保存配置类信息ImportRegistry ir = this.beanFactory.getBean(IMPORT_REGISTRY_BEAN_NAME, ImportRegistry.class);AnnotationMetadata importingClass = ir.getImportingClassFor(ClassUtils.getUserClass(bean).getName());if (importingClass != null) {((ImportAware) bean).setImportMetadata(importingClass);}}return bean;}
3. 总结时刻
推荐阅读
《ImportSelector 与 DeferredImportSelector 的区别》:Spring Boot 自动装配时,自定义配置为什么能覆盖自动装配的配置类?
每天用心记录一点点。内容也许不重要,但习惯很重要!
