【源码】Spring —— @Configuration 2 ConfigurationClassParser、Static 修饰配置类的优先级
前言
配置类的解析最终是交由 ConfigurationClassParser
来处理的,本章节结合 ConfigurationClassParser
源码解读 配置类
解析的部分细节
版本
springframework:5.2.x
ConfigurationClassParser
public void parse(Set<BeanDefinitionHolder> configCandidates) {
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
try {
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
// ...
}
}
// deferredImportSelector 最后才处理
this.deferredImportSelectorHandler.process();
}
-------------------------------------------------
protected final void parse(@Nullable String className, String beanName) throws IOException {
MetadataReader reader = this.metadataReaderFactory.getMetadataReader(className);
processConfigurationClass(new ConfigurationClass(reader, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected final void parse(Class<?> clazz, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(clazz, beanName), DEFAULT_EXCLUSION_FILTER);
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName), DEFAULT_EXCLUSION_FILTER);
}
parse(Set<BeanDefinitionHolder> configCandidates)
方法是对外的入口,接收配置类集合并进行解析- 根据配置类的
BeanDefinition
分发给对应的parse
- 这组方法的性质一致,都是入口级别
- 最终都是委托到
processConfigurationClass
方法
ConfigurationClassParser#processConfigurationClass
protected void processConfigurationClass(ConfigurationClass configClass, Predicate<String> filter) throws IOException {
// Condition 条件注解过滤
if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
return;
}
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
if (existingClass != null) {
/**
* 如果之前已经解析过,且当前该类是被 Import 的:
* 1)之前的类也是被 Import 的,那么合并 ImportedBy
* 场景如:一个配置类被重复 import
* 2)之前的类并不是 Import 的,那就已经处理过了,什么也不干
* 场景如:扫描路径下组件的静态内部类,在被作为成员类处理
* 之前已经被处理过
*/
if (configClass.isImported()) {
if (existingClass.isImported()) {
existingClass.mergeImportedBy(configClass);
}
return;
}
/**
* 如果之前已经解析过,但当前该类不是被 Import 的,
* 那么进行覆盖处理(即重新解析)
*/
else {
this.configurationClasses.remove(configClass);
this.knownSuperclasses.values().removeIf(configClass::equals);
}
}
// 递归解析,此处指递归父类
SourceClass sourceClass = asSourceClass(configClass, filter);
do {
sourceClass = doProcessConfigurationClass(configClass, sourceClass, filter);
}
while (sourceClass != null);
// 配置类的解析完成后才把它放到 configurationClasses 中
this.configurationClasses.put(configClass, configClass);
}
processConfigurationClass
方法是最全周期的配置类处理方法,它包括- 依赖
conditionEvaluator
进行条件过滤决定是否继续解析 - 重复
import
的处理 - 递归解析配置类
- 对上述步骤完成后才把它放到
configurationClasses
集合中,也就是说过程中解析的配置类是优先于当前配置类的
ConfigurationClassParser#doProcessConfigurationClass
所谓的配置类解析,实际上就是分别解析配置类的 成员类
、@PropertySource
注解、@ComponentScan
注解、@Import
注解、@ImportResource
注解 以及 @Bean
注解的方法,因此我们一个个解读
成员类解析
if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
// 解析成员类
processMemberClasses(configClass, sourceClass, filter);
}
---------------- processMemberClasses ----------------
private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass,
Predicate<String> filter) throws IOException {
// 获取所有成员类
Collection<SourceClass> memberClasses = sourceClass.getMemberClasses();
if (!memberClasses.isEmpty()) {
List<SourceClass> candidates = new ArrayList<>(memberClasses.size());
/**
* 遍历所有成员类,如果是配置类,则加入 candidates
*/
for (SourceClass memberClass : memberClasses) {
if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) &&
!memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) {
candidates.add(memberClass);
}
}
OrderComparator.sort(candidates);
for (SourceClass candidate : candidates) {
/**
* 利用 importStack 解决“重复解析”的问题
* 解析前将其压入 importStack
* 如果在出栈之前又被解析到,说明存在“循环引入”
*/
if (this.importStack.contains(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
// 嵌套解析目标成员配置类
processConfigurationClass(candidate.asConfigClass(configClass), filter);
}
finally {
this.importStack.pop();
}
}
}
}
}
- 针对目标成员类中的
配置类
,递归解析,这里的成员类包括静态内部类
和普通内部类
,且这里的普通内部类
优先级是高于静态内部类
的 - 利用
importStack
解决“重复解析”
的问题 - 对满足条件的成员配置类,是基于
processConfigurationClass
方法嵌套处理,不难理解,其对应BeanDefinition
的注册是先于主类的(当然不包括主配置类被注册的场景,比如主配置类是被路径扫描的)
关于解析成员配置类时 普通内部类 优先级高于 静态内部类 是个人验证的结果,没
有刻意去找官方结论
@PropertySource(s) 注解解析
for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(
sourceClass.getMetadata(), PropertySources.class,
org.springframework.context.annotation.PropertySource.class)) {
if (this.environment instanceof ConfigurableEnvironment) {
processPropertySource(propertySource);
}
else {
// ...
}
}
该注解用于从外部导入配置文件,从注解信息中获取对应路径的配置文件资源,解析成对应的 PropertySource
,维护到 Environment
中
关于 PropertySource,可以参考
【Spring】PropertySource 的解读与示例(MapPropertySource CommandLinePropertySource)
@ComponentScan(s) 注解解析
// 条件过滤
if (!componentScans.isEmpty() &&
!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {
/**
* 遍历所有 componentScan
* 交给 ComponentScanAnnotationParser componentScanParser 解析
*/
for (AnnotationAttributes componentScan : componentScans) {
Set<BeanDefinitionHolder> scannedBeanDefinitions =
this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
/**
* 解析到的所有 BeanDefinition,如果是配置类,
* 则继续进行解析
*/
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());
}
}
}
}
由 ComponentScanAnnotationParser componentScanParser
扫描所有的 componentScan
:
- 注册 对应的
BeanDefinition
并返回 - 对于其中的
配置类
,继续进行解析
整体上其实很好理解,但这里需要指出一个细节,可以引申如下 重要结论:扫描路径下
的配置类 Static
关键字修饰的 静态内部配置类 会被优先解析(注册),这其中的缘由需要追踪到方法 ComponentScanAnnotationParser#parse
ComponentScanAnnotationParser#parse
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);
// ...
return scanner.doScan(StringUtils.toStringArray(basePackages));
}
----------------------- doScan -----------------------
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
for (String basePackage : basePackages) {
// 获取所有的备选配置类
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
// ... 遍历处理注册对应的 BeanDefinition
}
}
return beanDefinitions;
}
-------------- findCandidateComponents --------------
public Set<BeanDefinition> findCandidateComponents(String basePackage) {
if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
}
else {
return scanCandidateComponents(basePackage);
}
}
-------------- scanCandidateComponents --------------
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {
Set<BeanDefinition> candidates = new LinkedHashSet<>();
try {
String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +
resolveBasePackage(basePackage) + '/' + this.resourcePattern;
// 读取所有的资源类
Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);
boolean traceEnabled = logger.isTraceEnabled();
boolean debugEnabled = logger.isDebugEnabled();
for (Resource resource : resources) {
if (traceEnabled) {
logger.trace("Scanning " + resource);
}
if (resource.isReadable()) {
try {
MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
/**
* 过滤判断一
* 1)非主类,因为一般是从主类扫描过来的,所以肯定排除自己
* 2)标注了 @Component 元注解的也算,即 @Configuration 注解也算)
* 3)或 标注有 javax.annotation.@ManagedBean 注解
*/
if (isCandidateComponent(metadataReader)) {
ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
sbd.setSource(resource);
/**
* 过滤判断二
* AnnotationMetadata metadata = beanDefinition.getMetadata();
* return (metadata.isIndependent() && (metadata.isConcrete() ||
* (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
*
* isIndependent():Top Level Class or Nested Class
* Top Level Class:顶层类,即普通类
* Inner Class:非静态内部类
* Nested Class:嵌套类(静态内部类)
* Local Class:方法内声明的局部类
* Anonymous Class:匿名类
*
* isConcrete:isInterface or isAbstract
*/
if (isCandidateComponent(sbd)) {
if (debugEnabled) {
logger.debug("Identified candidate component class: " + resource);
}
candidates.add(sbd);
}
else {
// ...
}
}
else {
// ...
}
}
catch (Throwable ex) {
// ...
}
}
else {
// ...
}
}
}
catch (IOException ex) {
// ...
}
return candidates;
}
调用链路很长,我们作一个总结:
- 首先对于所有的
componentScan
都是由ClassPathBeanDefinitionScanner
来扫描的 - 其次,
ClassPathBeanDefinitionScanner
在注册对应的BeanDefinition
前肯定是要扫描出所有的备选类,这部分逻辑聚焦于方法scanCandidateComponents
1)加载所有的资源类,见图
可以看到内部静态类是优先于主类被加载的
2)加载的所有资源类,要进行过滤筛选:首先,源头类(主类)肯定被排除,其次,至少要被元注解@Component
标注。
3)同时,成员内部类被过滤
,这里也很好理解:成员类的解析已经发生在之前的processMemberClasses
方法了(但对应BeanDefinition
的注册在后面)
4)配置类被加载后,对应的BeanDefinition
被注册,其中如果存在配置类,同样会被解析(递归)
5)因此,扫描路径下的配置类本身及其静态内部类的BeanDefinition
在此处注册,且静态内部类优先于主类
,而成员内部类的注册发生在之后
@Import 注解解析
processImports(configClass, sourceClass, getImports(sourceClass), filter, true);
----------------- processImports -----------------
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {
// 为空则返回
if (importCandidates.isEmpty()) {
return;
}
// 检查是否循环引用
if (checkForCircularImports && isChainedImportOnStack(configClass)) {
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
}
else {
this.importStack.push(configClass);
try {
for (SourceClass candidate : importCandidates) {
/**
* 如果是个 ImportSelector
* 对于 DeferredImportSelector,则会延迟处理
* 对于其他 ImportSelector,调用 selectImports 方法获取
* 所有类名,然后递归 processImports
*/
if (candidate.isAssignable(ImportSelector.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}
}
/**
* 如果是个 ImportBeanDefinitionRegistrar
* 则 configClass.addImportBeanDefinitionRegistrar
* (registrar, currentSourceClass.getMetadata())
* 后续 loadBeanDefinition 阶段处理
*/
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
/**
* 如果既不是 ImportSelector 也不是 ImportBeanDefinitionRegistrar
* 则直接当作一个 配置类 解析
* 大多数 ImportSelector 都是递归到这
*/
else {
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}
}
catch (BeanDefinitionStoreException ex) {
throw ex;
}
catch (Throwable ex) {
// ...
}
finally {
this.importStack.pop();
}
}
}
getImports
获取所有需要被import
的配置类,此处实际是@Import
注解的递归解析,比如Spring Boot auto-configure
的解析就发生在此处- 对于
DeferredImportSelector
,此处收集起来,上文提到在所有相关解析完成后才会统一处理这部分DeferredImportSelector
,这实际上是为了实现Conditional
条件注入,Spring Boot
的强大依托于此 - 对于其他
ImportSelector
,则获取其返回的类名,继续进行processImports
处理,当然最终都是递归成其他几种情况 - 对于
ImportBeanDefinitionRegistrar
,此处也是收集起来,但它的处理时机在最最后 - 对于普通类,则直接作为配置类再次
processConfigurationClass
,因此也不难理解,@Import
的配置类解析(注册)都在主配置类之前(当然不包括主配置类被注册的场景,比如主配置类是被路径扫描的)
@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);
}
}
该注解用于导入其他 配置资源
(比如 .groovy
.xml
),此处解析其属性,并最终维护在当前 ConfigurationClass
的 importedResources
属性中后续处理
@Bean 注解方法解析
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
所有的方法会被封装成 BeanMethod
实例,并最终维护在当前 ConfigurationClass
的 Set<BeanMethod> beanMethods
属性中后续处理
接口方法解析
processInterfaces(configClass, sourceClass);
----------------- processInterfaces -----------------
private void processInterfaces(ConfigurationClass configClass, SourceClass sourceClass) throws IOException {
/**
* 对于所有接口中注解了 @Bean 的默认方法,也会进行处理
*/
for (SourceClass ifc : sourceClass.getInterfaces()) {
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(ifc);
for (MethodMetadata methodMetadata : beanMethods) {
if (!methodMetadata.isAbstract()) {
configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
}
processInterfaces(configClass, ifc);
}
}
对于接口中标注了 @Bean
的默认方法,也会进行处理
父类处理
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();
}
}
return null;
继续 doProcessConfigurationClass
处理 配置类
的父类,如果没有,则返回 null
打断最外层的递归
总结
上述就是 ConfigurationClassParser
的解析细节,其中就 static
修饰配置类的场景做一些讨论,以 Spring Boot
场景为主:
- 大多数的场景,我们自定义的配置组件都是从主类以
@ComponentScan
的形式被扫描进去的,结合上文分析,此时最终配置类(及其内部BeanDefinition
)注册顺序为:static 修饰的内部配置类
先于Top Level 配置类
先于普通内部配置类
先于Top Level 配置类 @Import 的配置类
- 配置类被
@Import
的场景,大多数即Spring Boot 自动装配
的配置类,其优先级为普通内部配置类
先于static 修饰的内部配置类
先于Top Level 配置类 @Import 的配置类
先于Top Level 配置类
可以看到,只有 static 修饰的内部配置类
是稳定先于 Top Level 配置类
的,可能这就是为什么绝大多数的装配类都是以 static 修饰的内部配置类
的形式定义的吧
因此 @AutoConfigureBefore + @AutoConfigureAfter + static关键字 就可以控制自动装配
的顺序以完成各种场景的组件注册
个人认为应该尽量避免以其他机制比如 @Import 来控制,原因如上
上一篇:【源码】Spring —— @Configuration 1 ConfigurationClassPostProcessor
下一篇:【源码】Spring —— @Configuration 3 FULL 配置类的原理