本文章很多知识点基于Spring源码解析----@Configuration、@SpringBootApplication、@Bean
@Import标签可以动态的引入一些Configuration类,SpringBoot自动配置也基于这个注解动态引入了很多Conguration类,下面看下使用
将之前文章demo里的MyConfiguration类上的@Configuration注解去掉,加入MyImportDemo类,然后修改启动类,代码如下:
public class MyImportDemo implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
String myConfiguration =
"com.example.springboot.springbootdemo.bean.MyConfiguration";
return new String[]{myConfiguration};
}
}
@SpringBootApplication
@Import(MyImportDemo.class)
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
启动程序,几时MyConfiguration没有加@Configuration注解,也会被自动引入了,下面看下实现原理
具体解析代码在ConfigurationClassParser#doProcessConfigurationClass
,上篇文章分析了,这个方法是获取所有ConfigurationClass的地方,其中@Import就是在这里处理的
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
throws IOException {
//....
processImports(configClass, sourceClass, getImports(sourceClass), true);
//....
return null;
}
首先调用getImports
方法获取所有使用了@Import
注解引入的类的信息,然后调用processImports
进行处理
private Set<SourceClass> getImports(SourceClass sourceClass) throws IOException {
// 所有引入的类的信息
Set<SourceClass> imports = new LinkedHashSet<>();
// 访问过的类的信息,用来防止重复访问
Set<SourceClass> visited = new LinkedHashSet<>();
// 开始收集
collectImports(sourceClass, imports, visited);
return imports;
}
private void collectImports(SourceClass sourceClass, Set<SourceClass> imports, Set<SourceClass> visited)
throws IOException {
// 访问前先将当前类放入Set,返回true表示还未进行解析
if (visited.add(sourceClass)) {
//递归处理
for (SourceClass annotation : sourceClass.getAnnotations()) {
String annName = annotation.getMetadata().getClassName();
if (!annName.startsWith("java") && !annName.equals(Import.class.getName())) {
collectImports(annotation, imports, visited);
}
}
//将该类/注解上的@Import注解的属性放入imports集合中
imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
}
}
以SpringBootDemoApplication为入口,递归往上获取配置了@Import注解的信息,此时得到的数据如下:
当获取完成后,就调用processImports方法进行处理
private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, boolean checkForCircularImports) throws IOException {
for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {// ====1
// Import注解中配置的是ImportSelector类型
Class<?> candidateClass = candidate.loadClass();
//实例化
ImportSelector selector = BeanUtils.instantiateClass(candidateClass, ImportSelector.class);
// 如果该类有实现对应的Aware接口,则注入对应的属性
ParserStrategyUtils.invokeAwareMethods(
selector, this.environment, this.resourceLoader, this.registry);
// DeferredImportSelector类型的放到集合中待后续处理
if (this.deferredImportSelectors != null && selector instanceof DeferredImportSelector) {//====1.1
this.deferredImportSelectors.add(
new DeferredImportSelectorHolder(configClass, (DeferredImportSelector) selector));
}
else {//====1.2
// 非DeferredImportSelector类型,例如我们的写的Demo
// 直接调用selectImports方法取得对应的类
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
// 在调用processImports进行处理
processImports(configClass, currentSourceClass, importSourceClasses, false);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {//====2
// 如果是ImportBeanDefinitionRegistrar类型
Class<?> candidateClass = candidate.loadClass();
// 实例化
ImportBeanDefinitionRegistrar registrar =
BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class);
// 如果该类有实现对应的Aware接口,则注入对应的属性
ParserStrategyUtils.invokeAwareMethods(
registrar, this.environment, this.resourceLoader, this.registry);
// 同1.1先放到集合中待后续处理
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {//====3
// 如果非上面两种类型,那么代表这是一个与@Configuration相关的类
// 交由processConfigurationClass来进行Configuration进行
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass));
}
}
}
可以看到ImportBeanDefinitionRegistrar
和DeferredImportSelector
这两种类型,会放到集合当中待后续处理,而普通的ImportSelector
类型,例如MyImportDemo,这是一个ImportSelector
类型,那么会直接调用selectImports
方法获取对应的类。
注意:由于这里并不知道返回的类型是什么,所以又会调用递归调用processImports
进行再一次处理,直到最后返回的是非ImportSelector
类型为止,而MyImportDemo返回的是一个ConfigurationClass,所以会走到====3这个步骤,即交由processConfigurationClass
进行ConfigurationClass的解析。
接下来找到两个集合用到的地方,看下后续的解析逻辑
在ConfigurationClassParser#parse
方法最后会调用processDeferredImportSelectors进行DeferredImportSelector的解析
注意:从这个调用点来看DeferredImportSelector
比ImportSelector
更晚加载,而且他是在所有的Configuration解析完成之后才进行解析的
private void processDeferredImportSelectors() {
List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
this.deferredImportSelectors = null;
if (deferredImports == null) {
return;
}
deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
for (DeferredImportSelectorHolder deferredImport : deferredImports) {
ConfigurationClass configClass = deferredImport.getConfigurationClass();
// 和processImports方法的====1.2逻辑一样
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
processImports(configClass, asSourceClass(configClass), asSourceClasses(imports), false);
}
}
这里的逻辑和ImportSelector逻辑处理一致,这样处理只是为了延后处理,可以根据情况选择对应的类型
ImportBeanDefinitionRegistrar放到了importBeanDefinitionRegistrars这个Map中,处理逻辑在ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForConfigurationClass
中,这个方法中Spring源码解析----@Configuration、@SpringBootApplication、@Bean说过是解析@Bean注解的地方,但是该方法又调用了几个方法没有分析,其中loadBeanDefinitionsFromRegistrars
方法就是解析ImportBeanDefinitionRegistrar的地方
private void loadBeanDefinitionsFromRegistrars(Map<ImportBeanDefinitionRegistrar, AnnotationMetadata> registrars) {
registrars.forEach((registrar, metadata) ->
registrar.registerBeanDefinitions(metadata, this.registry));
}
这个方法逻辑也很简单,就是直接调用ImportBeanDefinitionRegistrar的registerBeanDefinitions方法,这个方法可以用来注册一下新的BeanDefinition到容器中
到此为止,结合Spring源码解析----@Configuration、@SpringBootApplication、@Bean的处理,整个流程可以简化如下:
另外本文没有分析具体引入的类的作用,这个引入不同的类有不同的作用,这里只需要明白@Import的原理,待后续分析到相关功能之后,便可以将知识点串起来