文章目录
- 前言
- 一、processConfigBeanDefinitions
- 1.1、checkConfigurationClassCandidate
- 1.2、parse
- 1.2.1、处理配置类标记了@Component 的情况
- 1.2.2、处理 @ComponentScan 注解
- 总结
前言
在Spring的注解模式中,通常在构造AnnotationConfigApplicationContext
时需要传入一个配置类
,标注有扫描的类路径,以及@Configuration
注解。
前篇中提到,解析配置类是在refresh方法的
invokeBeanFactoryPostProcessors
中,通过如图的三行关键代码,从容器中拿到ConfigurationClassPostProcessor
后置处理器,并执行其中的postProcessBeanDefinitionRegistry
方法完成的。
invokeBeanFactoryPostProcessors
ConfigurationClassPostProcessor 解析配置类
一、processConfigBeanDefinitions
processConfigBeanDefinitions
是进行配置类解析的关键逻辑。参数中会传入一个BeanDefinitionRegistry
,其中就能拿到自定义的配置类:
processConfigBeanDefinitions
方法的整体逻辑:
java">public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 存储符合条件的 @Configuration 类List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 获取当前 BeanDefinitionRegistry 中的所有 Bean 名称String[] candidateNames = registry.getBeanDefinitionNames();// 遍历所有 BeanDefinition,找出 @Configuration 类for (String beanName : candidateNames) {// 获取 BeanDefinitionBeanDefinition beanDef = registry.getBeanDefinition(beanName);// 检查该 BeanDefinition 是否已经被标记为 @Configuration 类if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 1.1、checkConfigurationClassCandidate 判断当前 BeanDefinition 是否是 @Configuration 配置类else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {// 如果是 @Configuration 类,则添加到 configCandidates 列表configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// 如果没有发现 @Configuration 类,直接返回if (configCandidates.isEmpty()) {return;}// 按照 @Order 注解的值进行排序,保证 @Configuration 类按照顺序解析configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// 检测是否有自定义的 Bean名称 生成策略SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {// 尝试从 SingletonBeanRegistry 获取 BeanName 生成器BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}// 如果环境变量 environment 为空,则使用默认的 StandardEnvironment// 在refresh的准备阶段设置过,通常不会为空if (this.environment == null) {this.environment = new StandardEnvironment();}// 创建 ConfigurationClassParser,用于解析 @Configuration 类ConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 将 configCandidates 转换为 Set 以去重,并存入 candidatesSet<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);// 记录已经解析过的 @Configuration 类,防止重复解析Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());// 进入循环解析配置类,处理 @Import、@Bean、@ComponentScan 等do {StartupStep processConfig = this.applicationStartup.start("spring.context.config-classes.parse");// 1.2、parse 解析 @Configuration 类parser.parse(candidates);parser.validate();// 获取解析出的 @Configuration 配置类Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());// 移除已经解析过的类,避免重复解析configClasses.removeAll(alreadyParsed);// 创建 ConfigurationClassBeanDefinitionReader 用于注册新的 BeanDefinitionif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 解析 @Configuration 类中的 @Bean 方法,注册为 BeanDefinitionthis.reader.loadBeanDefinitions(configClasses);// 将新解析的 @Configuration 类加入已解析列表alreadyParsed.addAll(configClasses);processConfig.tag("classCount", () -> String.valueOf(configClasses.size())).end();// 清空 candidates,准备检查是否有新加入的 @Configuration 类candidates.clear();// 检查在解析过程中是否新增了 BeanDefinitionif (registry.getBeanDefinitionCount() > candidateNames.length) {// 获取新的 BeanDefinition 名称String[] newCandidateNames = registry.getBeanDefinitionNames();// 记录原有的 BeanDefinition 名称Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));// 记录已经解析的 @Configuration 类名称Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}// 遍历新加入的 BeanDefinition,检查是否是 @Configuration 类for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);// 只有未解析过的 @Configuration 类才会被加入解析队列if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}// 更新 candidateNames,保证下一轮解析可以获取新加入的 BeanDefinitioncandidateNames = newCandidateNames;}}while (!candidates.isEmpty()); // 如果还有新的 @Configuration 类,就继续解析// 注册 ImportRegistry,用于支持 @ImportAware 机制if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}// 清理 MetadataReaderFactory 缓存,防止内存泄漏if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}
}
1.1、checkConfigurationClassCandidate
checkConfigurationClassCandidate
方法的作用,是判断遍历到的类上,是否加入了@Configuration
注解:
关键代码,根据类的元信息判断是否有@Configuration注解
其中的关键代码是这一段:
java">//根据类的元信息判断是否有@Configuration注解
Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());
//标记为full配置类
if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
}
//标记为lite配置类
else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);
@Configuration
注解有一个proxyBeanMethods
属性,默认为true: 如果
@Configuration
注解的proxyBeanMethods
属性为true,该配置类就会被标记为full,否则是lite,这两种模式会影响 @Bean 方法的调用方式 和 Spring 是否使用 CGLIB 代理。
- Full 模式下,Spring 使用 CGLIB 代理 来代理 @Configuration 配置类,使其成为一个 代理对象,该模式确保 @Bean 方法不会创建多个相同类型的实例
java">@Configuration(proxyBeanMethods = true) // Full 模式(默认)
public class AppConfig {@Beanpublic UserService userService() {//userService() 方法调用了 orderService() 方法。//orderService() 方法在 @Configuration 代理类内部被调用,因此 Spring 不会重新创建一个新的 OrderService,而是通过 代理方法 调用 Spring 容器中的 Bean,保证 userService 和其他 Bean 共享同一个 orderService 实例。return new UserService(orderService());}@Beanpublic OrderService orderService() {return new OrderService();}
}
- Lite 模式则相反,Spring 不会为 @Configuration 配置类生成 CGLIB 代理,配置类就是一个普通的 Java 类。@Bean 方法不会走 Spring 容器,而是直接调用普通方法,因此可能会 创建多个实例,不能保证 @Bean 作用域的一致性。
java">@Configuration(proxyBeanMethods = false) // Lite 模式
public class AppConfig {@Beanpublic UserService userService() {return new UserService(orderService()); // 直接调用方法,不经过 Spring 代理}@Beanpublic OrderService orderService() {return new OrderService();}
}
1.2、parse
parse是进行配置类解析的核心方法,最终调用的是ConfigurationClassParser
的doProcessConfigurationClass
,这里重点说明下处理@ComponentScan
注解的逻辑:
java">protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {// 1.2.1、 如果该配置类被 @Component 注解标记,则优先递归处理其内部类(嵌套类)if (configClass.getMetadata().isAnnotated(Component.class.getName())) {processMemberClasses(configClass, sourceClass, filter);}// 处理 @PropertySource 注解,加载外部属性配置文件for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);} else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// 1.2.2、处理 @ComponentScan 注解,扫描组件并注册到 Spring 容器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) {// 解析 @ComponentScan 扫描的 BeanSet<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// 遍历扫描得到的 BeanDefinitionHolder,检查是否有新的 @Configuration 类并递归解析for (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// 处理 @Import 注解,导入其他配置类、ImportSelector 或 ImportBeanDefinitionRegistrar//如果是ImportSelector,那么调用执行selectImports方法得到类名,然后在把这个类当做配置类进行解析(递归)//如果是ImportBeanDefinitionRegistrar,那么则生成一个ImportBeanDefinitionRegistrar实例对象,并添加到配置类对象中(ConfigurationClass)的importBeanDefinitionRegistrars属性中processImports(configClass, sourceClass, getImports(sourceClass), filter, true);// 处理 @ImportResource 注解,导入 XML 配置文件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);}}//处理 @Bean 方法,将其注册到配置类的 bean 方法集合中Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// 如果配置类实现了某些接口,则看这些接口内是否定义了@Bean的默认方法processInterfaces(configClass, sourceClass);// 处理当前类的超类(如果有),确保父类上的 @Configuration 也被解析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();}}// 没有超类需要处理,则返回 null,结束解析return null;
}
1.2.1、处理配置类标记了@Component 的情况
如果一个 @Configuration
类同时被 @Component
注解标记,则意味着该类本身是一个 Spring 组件,并且可能包含内部类(嵌套类):
java">@Configuration
@Component
public class MainConfig {@Beanpublic String mainBean() {return "Main Bean";}@Configurationpublic static class NestedConfig {@Beanpublic String nestedBean() {return "Nested Bean";}}
}
这种情况就需要进行递归处理
1.2.2、处理 @ComponentScan 注解
在这一步,会去对于@ComponentScan
的属性进行解析:
在
scanner.doScan
方法中,即是去扫描类路径下标注了@Component
及其衍生注解的类,生成bean定义。在Spring源码分析のBean扫描流程中有关于整个流程的说明。
但是有一种例外,即
@ComponentScan 发现新的 @Configuration
:
java">@Configuration
@ComponentScan("com.example")
public class AppConfig { }
com.example 目录下:
java">@Configuration
public class AdditionalConfig {@Beanpublic String myBean() {return "Hello";}
}
AppConfig 触发@ComponentScan
,扫描com.example目录时,发现AdditionalConfig是@Configuration
,就会递归调用parse方法进行解析。
总结
processConfigBeanDefinitions
的主线流程:
- 获取当前
BeanDefinitionRegistry
中的所有 Bean 名称。 - 遍历这些bean名称,找到标注了
@Configuration
注解的类。 - 按照
@Order
注解(如果有)的值对标注了@Configuration
注解的类进行排序。 - 创建
ConfigurationClassParser
解析器,用于解析。 - 循环解析配置类
parse
方法解析@Configuration
类- 如果该配置类被
@Component
注解标记,则优先递归处理其内部类(嵌套类)。 - 处理
@PropertySource
注解,加载外部属性配置文件。 - 处理
@ComponentScan
注解,扫描组件并注册到 Spring 容器。 - 处理
@Import
注解- 如果是ImportSelector,那么调用执行selectImports方法得到类名,然后在把这个类当做配置类进行解析(递归)。
- 如果是ImportBeanDefinitionRegistrar,那么则生成一个ImportBeanDefinitionRegistrar实例对象,并添加到配置类对象中(ConfigurationClass)的importBeanDefinitionRegistrars属性中。
- 处理
@ImportResource
注解,导入 XML 配置文件。 - 处理
@Bean
方法。 - 处理实现了接口以及有父类的情况。
- 如果该配置类被
- 解析
@Configuration
类中的@Bean
方法,注册为 BeanDefinition。 - 如果在解析的过程中,又产生了新的
@Configuration
类,则递归再次进行解析。
- 注册
ImportRegistry
,用于支持@ImportAware
机制。 - 进行最后的清理工作。