SpringBoot自动配置之ConfigurationCl

本文基于SpringBoot 2.5.7版本进行讲解

前文回顾:在上一篇文章,SpringBoot自动配置之AutoConfigurationImportSelector - SpringBoot自动配置(二),讲到AutoConfigurationImportSelectorimportSelector()方法通过调用getAutoConfigurationEntry()来获取需要自动配置的Bean信息。

但是,在第一篇文章,SpringBoot自动配置之@SpringBootApplication注解 - SpringBoot自动配置(一),我也讲到其实SpringBoot并没有通过AutoConfigurationImportSelector类的importSelector()方法来获取自动配置的Bean信息。

有些读者,可能也在AutoConfigurationImportSelector类的selectImports()方法断点了,结果发生debug的时候,程序根本就没有进入到这个方法。

看到这里,我们心中必定是十万个为什么?

  1. AutoConfigurationImportSelector不是实现了ImportSelector接口吗?为什么selectImports()方法没有被调用?
  2. 既然Spring Boot不通过AutoConfigurationImportSelectorselectImports()方法来获取需要自动配置的Bean信息,那么从哪里获取?

别急,这就是本文讲解的内容。让我们带着这个问题,一起看下去吧。

配置调试环境

既然,程序没有调用AutoConfigurationImportSelector类的selectImports()方法。那么,我们就自己创建一个简单的ImportSelector接口的实现类,然后看看SpringBoot会不会调用这个自定义的ImportSelector实现类。

需要被注入的Bean:Hello

1
2
3
4
5
6
java复制代码public class Hello {

public void print() {
System.out.println("hello word");
}
}

自定义ImportSelector实现类:HelloImportSelector

1
2
3
4
5
6
java复制代码public class HelloImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {"com.xgc.entity.Hello"};
}
}

SpringBoot启动类:SpringTestApplication

1
2
3
4
5
6
7
8
java复制代码@Import(HelloImportSelector.class)
@SpringBootApplication
public class SpringTestApplication {

public static void main(String[] args) {
SpringApplication.run(SpringTestApplication.class, args);
}
}

调试过程

到这里,就配置好了调试环境。接下来,我们在HelloImportSelector类的selectImports()方法打上断点,来看看程序会不会调用这个方法。

开始调试

image.png
看上图,我们知道SpringBoot程序是调用了selectImports()方法的。

追踪调用栈

既然,我们知道SpringBoot程序是会调用HelloImportSelector类的selectImports()方法,那么就好办了。

现在我们就沿着调用栈来一步一步追踪是哪里调用了这个selectImports()?

image.png

这里给出一部分调用栈的截图。我们看到,ConfigurationClassParserprocessImports()方法调用了HelloImportSelectorselectImports方法。

那么接下来,我们就来看看这个processImports()方法做了什么吧。

ConfigurationClassParser类的processImports()方法

下面这里给出processImports()方法的定义和部分源码:

(我们不用去看这个方法的定义和部分源码,这里列出只是为了我后面解释的时候,读者能够翻来对照,读者可以直接跳过方法源码直接看文字部分。)

定义:

1
2
3
java复制代码private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
boolean checkForCircularImports) {

部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
kotlin复制代码for (SourceClass candidate : importCandidates) {
if (candidate.isAssignable(ImportSelector.class)) {
// Candidate class is an ImportSelector -> delegate to it to determine imports
Class<?> candidateClass = candidate.loadClass();
ImportSelector selector = ParserStrategyUtils.instantiateClass(candidateClass, ImportSelector.class,
this.environment, this.resourceLoader, this.registry);
Predicate<String> selectorFilter = selector.getExclusionFilter();
if (selectorFilter != null) {
exclusionFilter = exclusionFilter.or(selectorFilter);
}
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);
}
}
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
// Candidate class is an ImportBeanDefinitionRegistrar ->
// delegate to it to register additional bean definitions
Class<?> candidateClass = candidate.loadClass();
ImportBeanDefinitionRegistrar registrar =
ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
this.environment, this.resourceLoader, this.registry);
configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
else {
// Candidate class not an ImportSelector or ImportBeanDefinitionRegistrar ->
// process it as an @Configuration class
this.importStack.registerImport(
currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
processConfigurationClass(candidate.asConfigClass(configClass), exclusionFilter);
}
}

这里就是文字部分了:

我们再精确点,看看是processImports()方法的那一部分代码调用了selectImports()方法。

1
2
3
4
5
6
7
8
9
java复制代码if (selector instanceof DeferredImportSelector) {
this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
}
else {
// 看这里,就是我调用了HelloImportSelector类的selectImports()方法
String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames, exclusionFilter);
processImports(configClass, currentSourceClass, importSourceClasses, exclusionFilter, false);
}

看到这个if-else语句,相信大家都懂了。

AutoConfigurationImportSelector类的selectImports()方法没有被调用,而我们自定义的HelloImportSelector方法的selectImports()方法被调用的原因就是:
AutoConfigurationImportSelector实现了DeferredImportSelector接口。

简单讲讲ConfigurationClassParserprocessImports()方法

现在我们翻回去看看刚刚上面贴出的processImports()的部分源码。
看代码的if-else语句判断部分就可以了。

processImports()方法会遍历importCandidates变量,然后判断里面的元素是否是ImportSelectorImportBeanDefinitionRegistrar的实现类,否则就是@Configuration类来处理。

我们也能猜到importCandidates变量就是@Import(xxx)里面的xxx类。

这就是为什么@Import注解能够接收ImportSelectorImportBeanDefinitionRegistrar实现类、@Configuration配置类以及普通的Bean对象,支持四种不同类型的类并完成Bean注入的原因。

@Import注解不了解的读者,可以看Spring的@Import注解四种使用方式进行了解。

SpringBoot怎么获取需要自动配置的Bean信息?

看过SpringBoot自动配置之AutoConfigurationImportSelector - SpringBoot自动配置(二)的知道,AutoConfigurationImportSelector实现了DeferredImportSelector接口,而DeferredImportSelector接口又实现了ImportSelector接口,所以AutoConfigurationImportSelector接口还是实现了selectImports()方法。

selectImports()方法中,我们又讲到selectImports()方法其实是通过调用getAutoConfigurationEntry()方法来拿到需要自动配置的bean信息。

既然我们在selectImports()方法断点发现SpringBoot并没有调用这个方法。那么我们不妨大胆猜想一下,SpringBoot最终会不会是直接通过调用getAutoConfigurationEntry()方法来获取需要自动配置的bean信息。

那么接下来,我们来求证一下。

image.png

可以看到SpringBoot确实是调用了这个方法来获取需要自动配置的bean信息。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%