SpringBoot自动配置之ConfigurationClassParser - SpringBoot自动配置(三)

894 阅读4分钟

本文基于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

public class Hello {

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

自定义ImportSelector实现类:HelloImportSelector

public class HelloImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[] {"com.xgc.entity.Hello"};
    }
}

SpringBoot启动类:SpringTestApplication

@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()方法的定义和部分源码:

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

定义:

private void processImports(ConfigurationClass configClass, SourceClass currentSourceClass,
      Collection<SourceClass> importCandidates, Predicate<String> exclusionFilter,
      boolean checkForCircularImports) {

部分源码:

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()方法。

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信息。