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. 过滤配置类 configCandidates
for (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. 解析 @PropertySource
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
}
// 3. 解析 @ComponentScan
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) {
// 3.1 委托给 componentScanParser
Set<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. 解析 @Import
processImports(configClass, sourceClass, getImports(sourceClass), true);
// 5. 解析 @ImportResource
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);
}
}
// 6. 解析 @Bean
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
// 7. default methods on interfaces
processInterfaces(configClass, sourceClass);
// 8. 解析父类 superclass
if (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注入ImportBeanDefinitionRegistrar
for (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 的情况。
@Configuration
public 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 时回调
@Override
public 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 自动装配时,自定义配置为什么能覆盖自动装配的配置类?
每天用心记录一点点。内容也许不重要,但习惯很重要!