【Spring源码】SpringBoot扫描与解析

作者: Weidan 分类: Spring源码 发布时间: 2020-01-02

SpringBoot

OK,现代 Java 开发者应该都对 SpringBoot 很熟悉了吧,一个很 "轻量级"IOC 容器。

我记得 SpringBoot 刚出来的时候,很多博客文章都会说 SpringBoot 减轻了 Java 开发工作者的负担,是个轻量级的框架。然而后面我才发现,并不轻量级。因为,SpringBoot 把需要依赖的东西给封装了起来,但其实比起之前自己控制依赖项目来说,反而会更重了一些,毕竟以前还可以自由组合。现在,SpringBoot 以及框架作者都提供了默认的依赖以及默认的配置,所以说这个框架轻量级其实并不是,要说轻量级应该只是说开发轻量级而已,菜鸟也可以快速上手建立一个后台项目而不必去关心太多项目配置的东西。

框架肯定是一个优秀的框架,我们项目全体也都是 SpringBoot 架构起来的,所以还是需要看看,SpringBoot 偷偷帮我们做了什么事情。

依赖简化

日常使用中,我们只需要引入一个 starter,就可以神奇把我们整合的框架整合起来。但是在以前,我们要使用 MyBatis 的时候,却需要引入 org.mybatis:mybatis 主框架,然后还因为需要整合 Spring 框架,所以我们还需要引入一个 org.mybatis:mybatis-spring。这就算了,这两个包如果版本号对不上,还要出现兼容性的问题。

然而现在我们只需要一个 org.mybatis.spring.boot:mybatis-spring-boot-starter,就可以同时引入这两个包,而且版本号还解决的很OK。所以现在我们可以看看他的 pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot</artifactId>
    <version>1.3.1</version>
  </parent>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <name>mybatis-spring-boot-starter</name>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
    </dependency>
  </dependencies>
</project>

所以结论是,一个 starter,定义了需要依赖的包的版本,然后通过依赖传递将这些包传递到我们 starter 所在的项目上来。

目前我建立了一个项目,这个项目很简单,只是依赖了一个 spring-boot-starter-web, 我们可以在 idea 上很方便的查看依赖的所有东西:

但其实这个做法在以前 Spring 的时候是已经存在了,这个项目就是 Spring-IO,但我也不知道为什么,可能因为名字取得不够好吧?然后这个项目好像很少人用。或者以前的人觉得会被依赖很多东西进来所以不用这个项目了?

自动装配

我们项目所需要的依赖都弄进来了,下一步就是配置让这些包可以相互配合,共同提供服务呀。

所以,SpringBoot 所提供的第二个功能就是,根据默认的配置,装配依赖进来包里面的 Bean 实例。依赖+装配,就是我们以前一直所需要的操作了,可以说这个框架减少了全世界程序员按 CTRL+C/V 的次数hhhhh。

OK,简单了解的话,我需要一个额外的 starter,这里就使用 org.mybatis.spring.boot:mybatis-spring-boot-starter 来看,仓库地址

项目的目录结构为:

├── LICENSE
├── README.md
├── license.txt
├── mvnw
├── mvnw.cmd
├── mybatis-spring-boot-autoconfigure
│   ├── format.xml
│   ├── license.txt
│   ├── pom.xml
│   └── src
├── mybatis-spring-boot-samples
│   ├── mybatis-spring-boot-sample-annotation
│   ├── mybatis-spring-boot-sample-freemarker
│   ├── mybatis-spring-boot-sample-freemarker-legacy
│   ├── mybatis-spring-boot-sample-groovy
│   ├── mybatis-spring-boot-sample-kotlin
│   ├── mybatis-spring-boot-sample-thymeleaf
│   ├── mybatis-spring-boot-sample-velocity
│   ├── mybatis-spring-boot-sample-velocity-legacy
│   ├── mybatis-spring-boot-sample-war
│   ├── mybatis-spring-boot-sample-web
│   ├── mybatis-spring-boot-sample-xml
│   └── pom.xml
├── mybatis-spring-boot-starter
│   ├── license.txt
│   └── pom.xml
├── mybatis-spring-boot-starter-test
│   └── pom.xml
├── mybatis-spring-boot-test-autoconfigure
│   ├── format.xml
│   ├── pom.xml
│   └── src
├── pom.xml
└── travis
    ├── after_success.sh
    └── settings.xml

其中,mybatis-spring-boot-autoconfigure 比较惹人注目,所以我们现在就看看这个项目。

首先我们看看 main/resources/META-INF/spring.factories,因为 Spring 很喜欢通过这些 meta 文件来促使各个模块很好的解耦但又能彼此配合工作,所以这个文件是定义 自动装配 开始的工厂类:

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

那么通过看初步的源码,这两个类都是熟悉的 Java 装配类,那么将会被加入到前面提到的 BeanFactory 容器中,后面解析将会调用里面的配置方法。

第二个优秀的地方就是,定义配置类,就可以在 yaml 文件中提示的出现,可以利用 IDE 工具很好的防止配置名写错。这个类就是 MybatisProperties.java

@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {

  public static final String MYBATIS_PREFIX = "mybatis";

  private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();

  /**
   * Location of MyBatis xml config file.
   */
  private String configLocation;

  /**
   * Locations of MyBatis mapper files.
   */
  private String[] mapperLocations;

  ...
}

OK,我们可以看到,定义了前缀就是 mybatis,而这个类是个贫血型的 Bean,只有属性。这些属性将会贯穿 MyBatis 在项目中整个生命周期。

接下来一个问题,我并不需要每个属性都在 yaml 文件中去定义啊,有些直接使用官方提供的默认值就可以了。

所以官方又贴心的提供了 additional-spring-configuration-metadata.json 这个 JSON 文件:

{
  "properties": [
    {
      "sourceType": "org.apache.ibatis.session.Configuration",
      "defaultValue": "org.apache.ibatis.scripting.xmltags.XMLLanguageDriver",
      "name": "mybatis.configuration.default-scripting-language",
      "description": "A default LanguageDriver class.",
      "type": "java.lang.Class<? extends org.apache.ibatis.scripting.LanguageDriver>",
      "deprecation": {
        "reason": "Because when this configuration property is used, there is case that custom language driver cannot be registered correctly.",
        "replacement": "mybatis.default-scripting-language-driver"
      }
    },
    {
      "sourceType": "org.apache.ibatis.session.Configuration",
      "defaultValue": "org.apache.ibatis.type.EnumTypeHandler",
      "name": "mybatis.configuration.default-enum-type-handler",
      "description": "A default TypeHandler class for Enum.",
      "type": "java.lang.Class<? extends org.apache.ibatis.type.TypeHandler>"
    },
    {
      "defaultValue": false,
      "name": "mybatis.lazy-initialization",
      "description": "Set whether enable lazy initialization for mapper bean.",
      "type": "java.lang.Boolean"
    },
    {
      "name": "mybatis.scripting-language-driver.velocity.userdirective",
      "deprecation": {
        "level": "error",
        "reason": "The 'userdirective' is deprecated since Velocity 2.x. This property defined for keeping backward compatibility with older velocity version.",
        "replacement": "mybatis.scripting-language-driver.velocity.velocity-settings.runtime.custom_directives"
      }
    }

  ]
}

不好理解的话,我们直接看第三个默认配置就好了:

{
  "defaultValue": false,
  "name": "mybatis.lazy-initialization",
  "description": "Set whether enable lazy initialization for mapper bean.",
  "type": "java.lang.Boolean"
}

看了下图应该明白了吧,上面那个文件就是定义配置的一些说明、默认值的。

所以当我们要造一个框架,又因为很多约定的东西,靠人脑来记已经靠不住的情况下,就可以编写类似于 additional-spring-configuration-metadata.json 这种文件来做约定以及说明了。


OK,上面说完了 SpringBoot 的两个最主要的优点以后,现在就来看看源码。但是源码这块,因为 传递依赖 利用的是 mvn/gradle 的特性,所以 依赖传递 并不需要再说。

那么最主要的就是来解析,怎么自动装配的问题。

简单的SpringBoot项目

pom.xml 依赖:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.liweidan.web</groupId>
    <artifactId>web-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>web-test</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

一个主启动器:

@SpringBootApplication
public class WebTestApplication {

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

}

一个控制器:

@RestController
public class UserController {

  @GetMapping("users")
  public List<Map<String, String>> users() {
    List<Map<String, String>> users = new ArrayList<>();
    for (int i = 0; i < 3; i++) {
      Map<String, String> u = new HashMap<>();
      u.put("id", String.valueOf(i));
      u.put("name", "name" + i);
      users.add(u);
    }
    return users;
  }

}

SpringApplication元信息准备

写过 SpringBoot 项目的同学应该对这个很熟悉了,通过传递一个上下文的根类,SpringBoot 将会自动装载在此类所在的包下面的所有类,并且 args 很明显就是我们在控制台传递的参数,也一并传递给 SpringApplication.run 这个方法,SpringBoot 即可将命令行的参数配置覆盖配置文件指定的配置。

接下来看看这个类做了什么事情:

public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
 return run(new Class<?>[] { primarySource }, args);
}

public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
 return new SpringApplication(primarySources).run(args);
}

通过静态方法,传递一个 Class 类作为主资源,然后再传递给主资源数组的 run 方法。那么这个数组其实我们是可以传递多个主资源的,比如我们做项目的时候,想要每个模块包彼此分离,即可传递多个包的主资源路径。

new SpringApplication(primarySources).run(args); 才是真正的进入容器的准备阶段:

首先看看构造器

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
 this.resourceLoader = resourceLoader;
 Assert.notNull(primarySources, "PrimarySources must not be null");
 // 初始化主资源链表,用于下面需要读取的时候可以遍历.
 this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
 // 判断Web环境,有webFlux/web/普通三个环境,主要通过类路径是否带有相对应需要的类来判断,如果都没有则初始化为普通Java项目.
 this.webApplicationType = WebApplicationType.deduceFromClasspath();
 // 初始化环境,这个接口多用于web环境,因为需要从web上下文加载一些信息.
 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
 // 初始化监听器,监听容器生命周期中需要回调的函数
 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
 // 从运行堆栈中寻找运行这个类的main方法所在的类
 this.mainApplicationClass = deduceMainApplicationClass();
}

加载不同模块的元信息

在上面的构造器中看到 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));Spring 还是跟以前获取 handlers 一样。通过在 META-INF 类加载路径定义不同的 spring.factories,使用类加载器读取这些配置文件资源,解析,加载配置文件中的类,初始化对象,来共同完成业务。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
 ClassLoader classLoader = getClassLoader();
 // Use names and ensure unique to protect against duplicates
 Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
 List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
 AnnotationAwareOrderComparator.sort(instances);
 return instances;
}

通过 SpringFactoriesLoader.loadFactoryNames(type, classLoader),加载当前 ClassLoader 中的指定类,源码显示如何读取,后面的 getOrDefault(factoryTypeName, Collections.emptyList()) 还是比较好理解的,如果加载到工厂类的名字就返回不然返回个空集合。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
 String factoryTypeName = factoryType.getName();
 return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
 // 相当于Map<String, List<String>>结构,先从缓存命中.
 MultiValueMap<String, String> result = cache.get(classLoader);
 if (result != null) {
  return result;
 }

 /*
 属性定义了元信息的路径以及名字
 public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
 */
 try {
  // 开始加载类路径下特定名字的元信息文件
  Enumeration<URL> urls = (classLoader != null ?
    classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
    ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
  result = new LinkedMultiValueMap<>();
  while (urls.hasMoreElements()) {
   URL url = urls.nextElement();
   UrlResource resource = new UrlResource(url);
   // 通过UrlResource解析成Properties对象
   Properties properties = PropertiesLoaderUtils.loadProperties(resource);
   // 遍历所有模块的指定文件,然后加入缓存
   for (Map.Entry<?, ?> entry : properties.entrySet()) {
    String factoryTypeName = ((String) entry.getKey()).trim();
    for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
     result.add(factoryTypeName, factoryImplementationName.trim());
    }
   }
  }
  cache.put(classLoader, result);
  return result;
 }
 catch (IOException ex) {
  throw new IllegalArgumentException("Unable to load factories from location [" +
    FACTORIES_RESOURCE_LOCATION + "]", ex);
 }
}

启动项目SpringApplication.run

可以看到 run 的源码跟之前的 AbstractApplicationContext#refresh 的味道还是一样的,先加载一系列容器运行时需要的生命周期类(Spring 模块间的也有用户自定义的),然后 refreshContext(context) 刷新容器上下文。

public ConfigurableApplicationContext run(String... args) {
 // 这是用来打印加载时间的工具类,暂时可以略过.
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 // 主要设置JVM虚拟机支持无设备情况下让awt可运行的属性
 configureHeadlessProperty();
 // 一. 加载所有SpringApplicationRunListeners并开始遍历所有Lintener启动监听回调函数
 SpringApplicationRunListeners listeners = getRunListeners(args);
 listeners.starting();
 try {
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  // 二. 开始准备ConfigurableEnvironment环境
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
  Banner printedBanner = printBanner(environment);
  // 三. 创建应用上下文
  context = createApplicationContext();
  exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);
  // 四. 做一些准备工作
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
  // 五. 刷新上下文
  refreshContext(context);
  // 六. 刷新完成后做的一些清理、回调工作
  afterRefresh(context, applicationArguments);
  stopWatch.stop();
  if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
  // 七. 启动完成后,调用所有SpringApplicationRunListener的完成启动的回调函数
  listeners.started(context);
  // 八. 主要处理ApplicationRunner和CommandLineRunner的回调
  callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, listeners);
  throw new IllegalStateException(ex);
 }

 try {
  // 九. 运行时的SpringApplicationRunListener回调函数
  listeners.running(context);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, null);
  throw new IllegalStateException(ex);
 }
 return context;
}

装载SpringApplicationRunListener

private SpringApplicationRunListeners getRunListeners(String[] args) {
 Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
 return new SpringApplicationRunListeners(logger,
   getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}

首先我们看第一步,加载所有的 SpringApplicationRunListener 子类,这个加载方式跟上面所说的加载元信息是一样的,只不过指定加载 SpringApplicationRunListener.class 类。

我们知道,Spring 经常会定义很多生命周期回调,供用户根据需求切入框架。这次 SpringBoot 的生命周期是 SpringApplicationRunListener

首先看看 SpringApplicationRunListener 定义哪些生命周期函数:

public interface SpringApplicationRunListener {

 // 项目开始时调用
 default void starting() {
 }

 // 环境准备好时
 default void environmentPrepared(ConfigurableEnvironment environment) {
 }

 // 上下文准备好时
 default void contextPrepared(ConfigurableApplicationContext context) {
 }

 // 上下文读取完成
 default void contextLoaded(ConfigurableApplicationContext context) {
 }

 // 启动完成
 default void started(ConfigurableApplicationContext context) {
 }

 // 项目运行时调用
 default void running(ConfigurableApplicationContext context) {
 }

 // 项目失败时
 default void failed(ConfigurableApplicationContext context, Throwable exception) {
 }

}

准备ConfigurableEnvironment环境

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
  ApplicationArguments applicationArguments) {
 // 首先根据环境类型获取对象的配置实现对象
 ConfigurableEnvironment environment = getOrCreateEnvironment();
 // 配置ConversionService单例对象,以及判断有没有设置Profiles环境
 configureEnvironment(environment, applicationArguments.getSourceArgs());
 // 读取其他环境中(比如web.xml)读取的配置属性
 ConfigurationPropertySources.attach(environment);
 // 继续上面的SpringApplicationRunListener生命周期调用
 listeners.environmentPrepared(environment);
 // 把环境绑定到Binder中,Binder是Spring提供的一个记录对象的容器
 bindToSpringApplication(environment);
 if (!this.isCustomEnvironment) {
  // 如果当前的配置环境和重新判断的环境不同,则转换成当前的环境(有可能在生命周期修改了加载的环境?)
  environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
    deduceEnvironmentClass());
 }
 // 重新读取特定环境的配置
 ConfigurationPropertySources.attach(environment);
 return environment;
}

private ConfigurableEnvironment getOrCreateEnvironment() {
  if (this.environment != null) {
    return this.environment;
  }
  switch (this.webApplicationType) {
    case SERVLET:
      return new StandardServletEnvironment();
    case REACTIVE:
      return new StandardReactiveWebEnvironment();
    default:
      return new StandardEnvironment();
  }
}

然后在主线程中配置忽略 BooleanBean

public ConfigurableApplicationContext run(String... args) {
    //...
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
  //...
}

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
  if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
    Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
    System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
  }
}

创建上下文

context = createApplicationContext();
// 获取模块的错误解释器,用于项目启动的时候解析是什么错误
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                                                 new Class[] { ConfigurableApplicationContext.class }, context);

根据不同环境创建不同的上下文:

protected ConfigurableApplicationContext createApplicationContext() {
 Class<?> contextClass = this.applicationContextClass;
 if (contextClass == null) {
  try {
   switch (this.webApplicationType) {
   case SERVLET:
    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
    break;
   case REACTIVE:
    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
    break;
   default:
    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
   }
  }
  catch (ClassNotFoundException ex) {
   throw new IllegalStateException(
     "Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
  }
 }
 return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

Context准备工作

    private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
    // 设置上面准备好的环境信息
        context.setEnvironment(environment);
    // 处理之前做一些事情,但是当前环境下,只是注册了一个 ConversionService 到 BeanFactory 中
        postProcessApplicationContext(context);
    // 调用之前注册的所有Initializers
        applyInitializers(context);
    // 再调用所有的listeners
        listeners.contextPrepared(context);
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }
        // 注册SpringBoot独有的一些Bean
        ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
        beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            beanFactory.registerSingleton("springBootBanner", printedBanner);
        }
    // 接下来跟之前一样
        if (beanFactory instanceof DefaultListableBeanFactory) {
            ((DefaultListableBeanFactory) beanFactory)
                    .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
        }
        if (this.lazyInitialization) {
            context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
        }
        // Load the sources
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
    // 根据我们传递的主类进行读取,放入BeanFactory中
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }

其实当前 postProcessApplicationContext 除了注册 ConversionService 以外其他事情都没做。

refreshContext刷新上下文

因为我要看看自动装配的问题,所以这个时候为了故事比较好说,我加了 mybatis-spring-boot-starter

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.BUILD-SNAPSHOT</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>cn.liweidan.web</groupId>
    <artifactId>web-test</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>web-test</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.197</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

刷新上下文:

private void refreshContext(ConfigurableApplicationContext context) {
 refresh(context);
 if (this.registerShutdownHook) {
  try {
   context.registerShutdownHook();
  }
  catch (AccessControlException ex) {
   // Not allowed in some environments.
  }
 }
}
protected void refresh(ApplicationContext applicationContext) {
  Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
  ((AbstractApplicationContext) applicationContext).refresh();
}

然后接下来就去到我们熟悉的 AbstractApplicationContext 中,重温一下这个 refresh() 方法:

@Override
public void refresh() throws BeansException, IllegalStateException {
  synchronized (this.startupShutdownMonitor) {
    // Prepare this context for refreshing.
    prepareRefresh();

    // Tell the subclass to refresh the internal bean factory.
    ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

    // Prepare the bean factory for use in this context.
    prepareBeanFactory(beanFactory);

    try {
      // Allows post-processing of the bean factory in context subclasses.
      postProcessBeanFactory(beanFactory);

      // Invoke factory processors registered as beans in the context.
      invokeBeanFactoryPostProcessors(beanFactory);

      // Register bean processors that intercept bean creation.
      registerBeanPostProcessors(beanFactory);

      // Initialize message source for this context.
      initMessageSource();

      // Initialize event multicaster for this context.
      initApplicationEventMulticaster();

      // Initialize other special beans in specific context subclasses.
      onRefresh();

      // Check for listener beans and register them.
      registerListeners();

      // Instantiate all remaining (non-lazy-init) singletons.
      finishBeanFactoryInitialization(beanFactory);

      // Last step: publish corresponding event.
      finishRefresh();
    }

    catch (BeansException ex) {
      if (logger.isWarnEnabled()) {
        logger.warn("Exception encountered during context initialization - " +
                    "cancelling refresh attempt: " + ex);
      }

      // Destroy already created singletons to avoid dangling resources.
      destroyBeans();

      // Reset 'active' flag.
      cancelRefresh(ex);

      // Propagate exception to caller.
      throw ex;
    }

    finally {
      // Reset common introspection caches in Spring's core, since we
      // might not ever need metadata for singleton beans anymore...
      resetCommonCaches();
    }
  }
}

同之前一样,在 invokeBeanFactoryPostProcessors(beanFactory) 这句话出现了 BeanDefinition 数量的剧增,那么我们可以推断出,这句话还是关键,就是在这句话开始解析我们的项目依赖。

其实按照我的猜测,自动配置应该是 starter 提供了一些配置类交给 SpringBoot 注入 Spring 的 BeanFactory 中,但是现在看来,好像 SpringBoot 就不需要做什么东西,直接委托给容器扫描解析执行了。

invokeBeanFactoryPostProcessors

protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
 // 利用委派模式委托给 PostProcessorRegistrationDelegate 进行执行
 PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());

 // Detect a LoadTimeWeaver and prepare for weaving, if found in the meantime
 // (e.g. through an @Bean method registered by ConfigurationClassPostProcessor)
 if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean(LOAD_TIME_WEAVER_BEAN_NAME)) {
  beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory));
  beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader()));
 }
}

PostProcessorRegistrationDelegate委派对象

这是一个在 Context 模块下对象,主要处理 PostProcessor 相关的事情,看到签名只提供两个方法:

  • invokeBeanFactoryPostProcessors 执行 BF 的 PostProcessors
  • registerBeanPostProcessors 注册 PostProcessors 到 BF 中

关于普通的配置类解析,之前在 chapters3_高级的Beafctory—Spring上下文 已经有提到,解析项目中配置类不明白的话可以先看看那篇文章。

不过我们现在重点是怎么读取到 starter 框架里边的自动配置信息:

private static void invokeBeanDefinitionRegistryPostProcessors(
  Collection<? extends BeanDefinitionRegistryPostProcessor> postProcessors, BeanDefinitionRegistry registry) {

 for (BeanDefinitionRegistryPostProcessor postProcessor : postProcessors) {
  postProcessor.postProcessBeanDefinitionRegistry(registry);
 }
}

当前 postProcessors 里边只有一个 ConfigurationClassPostProcessor

那我们进入对应的 postProcessBeanDefinitionRegistry 方法:

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
 int registryId = System.identityHashCode(registry);
 if (this.registriesPostProcessed.contains(registryId)) {
  throw new IllegalStateException(
    "postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);
 }
 if (this.factoriesPostProcessed.contains(registryId)) {
  throw new IllegalStateException(
    "postProcessBeanFactory already called on this post-processor against " + registry);
 }
 this.registriesPostProcessed.add(registryId);

 processConfigBeanDefinitions(registry);
}

processConfigBeanDefinitions 解析配置

由于我们现在需要跟自动导入的配置,所以我们应该需要跟的类配置是我们的主类,也就是 WebTestApplication 这个类,这个类上修饰的注解 @SpringBootApplication 是自动注入的关键

这个才是执行自动配置的重点之重:

/**
 * Build and validate a configuration model based on the registry of
 * {@link Configuration} classes.
 */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
 List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
 String[] candidateNames = registry.getBeanDefinitionNames();

 for (String beanName : candidateNames) {
  BeanDefinition beanDef = registry.getBeanDefinition(beanName);
  if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
   if (logger.isDebugEnabled()) {
    logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);
   }
  }
  else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
   configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
  }
 }

 // Return immediately if no @Configuration classes were found
 if (configCandidates.isEmpty()) {
  return;
 }

 // Sort by previously determined @Order value, if applicable
 configCandidates.sort((bd1, bd2) -> {
  int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
  int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
  return Integer.compare(i1, i2);
 });

 // Detect any custom bean name generation strategy supplied through the enclosing application context
 SingletonBeanRegistry sbr = null;
 if (registry instanceof SingletonBeanRegistry) {
  sbr = (SingletonBeanRegistry) registry;
  if (!this.localBeanNameGeneratorSet) {
   BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
     AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
   if (generator != null) {
    this.componentScanBeanNameGenerator = generator;
    this.importBeanNameGenerator = generator;
   }
  }
 }

 if (this.environment == null) {
  this.environment = new StandardEnvironment();
 }

 // Parse each @Configuration class
 ConfigurationClassParser parser = new ConfigurationClassParser(
   this.metadataReaderFactory, this.problemReporter, this.environment,
   this.resourceLoader, this.componentScanBeanNameGenerator, registry);

 Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
 Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
 // ↑ 这上面属于解析开始的准备工作 ↑
 do {
  parser.parse(candidates);
  parser.validate();

  Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
  configClasses.removeAll(alreadyParsed);

  // Read the model and create bean definitions based on its content
  if (this.reader == null) {
   this.reader = new ConfigurationClassBeanDefinitionReader(
     registry, this.sourceExtractor, this.resourceLoader, this.environment,
     this.importBeanNameGenerator, parser.getImportRegistry());
  }
  this.reader.loadBeanDefinitions(configClasses);
  alreadyParsed.addAll(configClasses);

  candidates.clear();
  if (registry.getBeanDefinitionCount() > candidateNames.length) {
   String[] newCandidateNames = registry.getBeanDefinitionNames();
   Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
   Set<String> alreadyParsedClasses = new HashSet<>();
   for (ConfigurationClass configurationClass : alreadyParsed) {
    alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
   }
   for (String candidateName : newCandidateNames) {
    if (!oldCandidateNames.contains(candidateName)) {
     BeanDefinition bd = registry.getBeanDefinition(candidateName);
     if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
       !alreadyParsedClasses.contains(bd.getBeanClassName())) {
      candidates.add(new BeanDefinitionHolder(bd, candidateName));
     }
    }
   }
   candidateNames = newCandidateNames;
  }
 }
 while (!candidates.isEmpty());

 // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
 if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
  sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
 }

 if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
  // Clear cache in externally provided MetadataReaderFactory; this is a no-op
  // for a shared cache since it'll be cleared by the ApplicationContext.
  ((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
 }
}

重点看看 parser.parse(candidates); 这句话是怎么解析的:

public void parse(Set<BeanDefinitionHolder> configCandidates) {
 for (BeanDefinitionHolder holder : configCandidates) {
  BeanDefinition bd = holder.getBeanDefinition();
  try {
   if (bd instanceof AnnotatedBeanDefinition) {
    // 因为这是一个使用注解的Bean,所以应该关注这里
    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) {
   throw new BeanDefinitionStoreException(
     "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
  }
 }

 this.deferredImportSelectorHandler.process();
}

protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
  processConfigurationClass(new ConfigurationClass(metadata, beanName));
}

protected void processConfigurationClass(ConfigurationClass configClass) throws IOException {
  // 如果当前的类是一个解析类,则跳过解析
  if (this.conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.PARSE_CONFIGURATION)) {
    return;
  }

  // 判断是否已经解析过了
  ConfigurationClass existingClass = this.configurationClasses.get(configClass);
  if (existingClass != null) {
    if (configClass.isImported()) {
      if (existingClass.isImported()) {
        existingClass.mergeImportedBy(configClass);
      }
      // Otherwise ignore new imported config class; existing non-imported class overrides it.
      return;
    }
    else {
      // Explicit bean definition found, probably replacing an import.
      // Let's remove the old one and go with the new one.
      this.configurationClasses.remove(configClass);
      this.knownSuperclasses.values().removeIf(configClass::equals);
    }
  }

  // 递归解析配置类以及他的父类、接口等等
  SourceClass sourceClass = asSourceClass(configClass);
  do {
    sourceClass = doProcessConfigurationClass(configClass, sourceClass);
  }
  while (sourceClass != null);

  this.configurationClasses.put(configClass, configClass);
}

然后我们要进入 doProcessConfigurationClass 观察解析的过程。

这串代码其实有点长……:

@Nullable
protected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
  throws IOException {

 if (configClass.getMetadata().isAnnotated(Component.class.getName())) {
  // Recursively process any member (nested) classes first
  processMemberClasses(configClass, sourceClass);
 }

 // 解析需要导入Property配置文件的类
 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");
  }
 }

 // 解析 @ComponentScan 
 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) {
   // The config class is annotated with @ComponentScan -> perform the scan immediately
   Set<BeanDefinitionHolder> scannedBeanDefinitions =
     this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
   // Check the set of scanned definitions for any further config classes and parse recursively if needed
   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 的类,这句话是重点
 processImports(configClass, sourceClass, getImports(sourceClass), true);

 // Process any @ImportResource annotations
 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 方法
 Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
 for (MethodMetadata methodMetadata : beanMethods) {
  configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
 }

 // 解析接口默认方法
 processInterfaces(configClass, sourceClass);

 // 如果有父类,返回父类,由上一层继续调用这个方法继续解析
 if (sourceClass.getMetadata().hasSuperClass()) {
  String superclass = sourceClass.getMetadata().getSuperClassName();
  if (superclass != null && !superclass.startsWith("java") &&
    !this.knownSuperclasses.containsKey(superclass)) {
   this.knownSuperclasses.put(superclass, configClass);
   // Superclass found, return its annotation metadata and recurse
   return sourceClass.getSuperClass();
  }
 }

 // 返回Null表示解析结束
 return null;
}

好了,可以看到通过 Java 类的解析可谓覆盖到尼玛我想象不到的地方….但他们就是应该要有

挑重点来看吧,我的主类没有父级也没有接口,直接重点看 processImports(configClass, sourceClass, getImports(sourceClass), true) 这句话。

这句话首先需要获取所有的导入配置 getImports(sourceClass)

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 {

 if (visited.add(sourceClass)) {
  for (SourceClass annotation : sourceClass.getAnnotations()) {
   String annName = annotation.getMetadata().getClassName();
   if (!annName.equals(Import.class.getName())) {
    collectImports(annotation, imports, visited);
   }
  }
  imports.addAll(sourceClass.getAnnotationAttributes(Import.class.getName(), "value"));
 }
}

这两个东西,切忌不要去 debug 一个一个看….我在这里看晕了好几个小时,应该是条件断点判断 sourceClass.getAnnotationAttributes(Import.class.getName(), "value").size() > 0 再停住

好了等到断点停住的时候,发现是 @interface SpringBootApplication > @interface EnableAutoConfiguration 上面的 @Import(AutoConfigurationImportSelector.class) 在这里导入了。以及这个类上边的 @AutoConfigurationPackage 导入了 @Import(AutoConfigurationPackages.Registrar.class)

所以当前需要解析的 importCandidatesAutoConfigurationPackages.Registrar.class 以及 AutoConfigurationImportSelector.class 两个类。

那么这两个类是什么用的,我现在也还不知道…先知道他被导入就好了,接下去看 processImports 方法了,这个方法应该有答案:

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

 if (importCandidates.isEmpty()) {
  return;
 }

 // 由上面传递的 checkForCircularImports 参数决定是否判断有没有循环引入
 if (checkForCircularImports && isChainedImportOnStack(configClass)) {
  this.problemReporter.error(new CircularImportProblem(configClass, this.importStack));
 }
 else {
  this.importStack.push(configClass);
  try {
   // 开始解析导入类
   for (SourceClass candidate : importCandidates) {
    // 第一层,根据不同的导入类型进行解析
    // AutoConfigurationImportSelector 来到这个分支
    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);
     if (selector instanceof DeferredImportSelector) {
      this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
     }
     else {
      String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
      Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
      // 递归解析
      processImports(configClass, currentSourceClass, importSourceClasses, false);
     }
    }
    // AutoConfigurationPackages.Registrar进入这个分支
    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 {
     // 直接以@Configuration的方式进行处理
     this.importStack.registerImport(
       currentSourceClass.getMetadata(), candidate.getMetadata().getClassName());
     processConfigurationClass(candidate.asConfigClass(configClass));
    }
   }
  }
  catch (BeanDefinitionStoreException ex) {
   throw ex;
  }
  catch (Throwable ex) {
   throw new BeanDefinitionStoreException(
     "Failed to process import candidates for configuration class [" +
     configClass.getMetadata().getClassName() + "]", ex);
  }
  finally {
   this.importStack.pop();
  }
 }
}

不行呀,因为需要根据类型来做,先给两个类的类型吧:

image-20191231144929006

image-20191231145022475

这三个分支对应着三个不同的配置类型:

  1. ImportSelector 用于导入配置类的信息
  2. Registrar 用于解析自定义注解动态生成 Bean 的信息
  3. @Configuration 读取到配置类,直接解析

那我们就先按照上图的顺序来解析这些配置类吧,首先是 AutoConfigurationPackages.Registrar 对象的解析。我先从上面一大串截取代码片段来看:

// ...
else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
 // 额外注册BeanDefinition用
 Class<?> candidateClass = candidate.loadClass();
 ImportBeanDefinitionRegistrar registrar =
   ParserStrategyUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class,
     this.environment, this.resourceLoader, this.registry);
 // 简单的添加(导入)到当前的 configClass 中
 configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
}
// ...

先放到后面,应该是解析的时候会调用到。

接下来看第二个 AutoConfigurationImportSelector.class 的解析:

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);
 if (selector instanceof DeferredImportSelector) {
  // 进入这里
  this.deferredImportSelectorHandler.handle(configClass, (DeferredImportSelector) selector);
 }
 else {
  String[] importClassNames = selector.selectImports(currentSourceClass.getMetadata());
  Collection<SourceClass> importSourceClasses = asSourceClasses(importClassNames);
  processImports(configClass, currentSourceClass, importSourceClasses, false);
 }
}

这一块比较麻烦,在上面 uml 可以看到,AutoConfigurationImportSelector.class 属于 DeferredImportSelector,所以交给 deferredImportSelectorHandler 进行处理。

public void handle(ConfigurationClass configClass, DeferredImportSelector importSelector) {
 DeferredImportSelectorHolder holder = new DeferredImportSelectorHolder(
   configClass, importSelector);
 if (this.deferredImportSelectors == null) {
  DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
  handler.register(holder);
  handler.processGroupImports();
 }
 else {
  // 来到这里,简单的加入
  this.deferredImportSelectors.add(holder);
 }
}

导入刚刚注册的配置

上面一大堆全是解析,然后接下来就要处理这些自动配置了。

回到 parser.parse(candidates); 这个方法:

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) {
   throw new BeanDefinitionStoreException(
     "Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);
  }
 }

 // 处理导入配置类(第三方配置)
 this.deferredImportSelectorHandler.process();
}

AutoConfigurationImportSelector解析

public void process() {
 // AutoConfigurationImportSelector集合 只有一个元素
 List<DeferredImportSelectorHolder> deferredImports = this.deferredImportSelectors;
 this.deferredImportSelectors = null;
 try {
  if (deferredImports != null) {
   DeferredImportSelectorGroupingHandler handler = new DeferredImportSelectorGroupingHandler();
   deferredImports.sort(DEFERRED_IMPORT_COMPARATOR);
   // 注册到 DeferredImportSelectorGroupingHandler 中
   deferredImports.forEach(handler::register);
   // 重点看这边
   handler.processGroupImports();
  }
 }
 finally {
  this.deferredImportSelectors = new ArrayList<>();
 }
}
public void processGroupImports() {
 for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
  grouping.getImports().forEach(entry -> {
   ConfigurationClass configurationClass = this.configurationClasses.get(
     entry.getMetadata());
   try {
    processImports(configurationClass, asSourceClass(configurationClass),
      asSourceClasses(entry.getImportClassName()), false);
   }
   catch (BeanDefinitionStoreException ex) {
    throw ex;
   }
   catch (Throwable ex) {
    throw new BeanDefinitionStoreException(
      "Failed to process import candidates for configuration class [" +
        configurationClass.getMetadata().getClassName() + "]", ex);
   }
  });
 }
}

先进入这里 grouping.getImports()

public Iterable<Group.Entry> getImports() {
 for (DeferredImportSelectorHolder deferredImport : this.deferredImports) {
  this.group.process(deferredImport.getConfigurationClass().getMetadata(),
    deferredImport.getImportSelector());
 }
 return this.group.selectImports();
}

这个过程就是 SpringBoot 提供的,在类路径提供 additional-spring-configuration-metadata.json 提供导入配置的,然而看起来好像所有的 SpringBoot 模块都在这里了,他只是通过导入类路径的包来判断是否要加载配置。

跳过这一段吧,看着有点累。我们现在只要知道,读取了类路径下的 additional-spring-configuration-metadata.json 后,我们想要看到的 MybatisAutoConfiguration 已经被读取到了就好了。

image-20200102110235579

同样的,spring-boot-mybatis-starter 也提供了上面的注册文件:

好了,接下来回到上面的解析方法:

public void processGroupImports() {
 for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
  grouping.getImports().forEach(entry -> {
   ConfigurationClass configurationClass = this.configurationClasses.get(
     entry.getMetadata());
   try {
    // 此时进入processImports的时候就不需要再导入配置了
    // 直接以 @Configuration 去实现配置
    processImports(configurationClass, asSourceClass(configurationClass),
      asSourceClasses(entry.getImportClassName()), false);
   }
   catch (BeanDefinitionStoreException ex) {
    throw ex;
   }
   catch (Throwable ex) {
    throw new BeanDefinitionStoreException(
      "Failed to process import candidates for configuration class [" +
        configurationClass.getMetadata().getClassName() + "]", ex);
   }
  });
 }
}

那么关于 @Configuration 的解析,在我 之前的文章 就可以看到了。

现在就看看怎么根据主类的所在的包,扫描该包下的所有 Bean

自动包扫描注册Bean

我们得回到 doProcessConfigurationClass 的这个地方:

 // 解析 @ComponentScan,会从主类 WebTestApplication 中拿到 BasePackage
 // 然后根据这个 BasePackage 的配置进行扫描下面的类
 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) {
   // 通过 componentScanParser 进行解析
   Set<BeanDefinitionHolder> scannedBeanDefinitions =
     this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());
   // Check the set of scanned definitions for any further config classes and parse recursively if needed
   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());
    }
   }
  }
 }
public Set<BeanDefinitionHolder> parse(AnnotationAttributes componentScan, final String declaringClass) {
 ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(this.registry,
   componentScan.getBoolean("useDefaultFilters"), this.environment, this.resourceLoader);

 Class<? extends BeanNameGenerator> generatorClass = componentScan.getClass("nameGenerator");
 boolean useInheritedGenerator = (BeanNameGenerator.class == generatorClass);
 scanner.setBeanNameGenerator(useInheritedGenerator ? this.beanNameGenerator :
   BeanUtils.instantiateClass(generatorClass));

 ScopedProxyMode scopedProxyMode = componentScan.getEnum("scopedProxy");
 if (scopedProxyMode != ScopedProxyMode.DEFAULT) {
  scanner.setScopedProxyMode(scopedProxyMode);
 }
 else {
  Class<? extends ScopeMetadataResolver> resolverClass = componentScan.getClass("scopeResolver");
  scanner.setScopeMetadataResolver(BeanUtils.instantiateClass(resolverClass));
 }

 scanner.setResourcePattern(componentScan.getString("resourcePattern"));

 for (AnnotationAttributes filter : componentScan.getAnnotationArray("includeFilters")) {
  for (TypeFilter typeFilter : typeFiltersFor(filter)) {
   scanner.addIncludeFilter(typeFilter);
  }
 }
 for (AnnotationAttributes filter : componentScan.getAnnotationArray("excludeFilters")) {
  for (TypeFilter typeFilter : typeFiltersFor(filter)) {
   scanner.addExcludeFilter(typeFilter);
  }
 }

 boolean lazyInit = componentScan.getBoolean("lazyInit");
 if (lazyInit) {
  scanner.getBeanDefinitionDefaults().setLazyInit(true);
 }

 Set<String> basePackages = new LinkedHashSet<>();
 String[] basePackagesArray = componentScan.getStringArray("basePackages");
 for (String pkg : basePackagesArray) {
  String[] tokenized = StringUtils.tokenizeToStringArray(this.environment.resolvePlaceholders(pkg),
    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
  Collections.addAll(basePackages, tokenized);
 }
 for (Class<?> clazz : componentScan.getClassArray("basePackageClasses")) {
  basePackages.add(ClassUtils.getPackageName(clazz));
 }
 if (basePackages.isEmpty()) {
  // 没有指定扫描包,进入这里获取主类的包名
  basePackages.add(ClassUtils.getPackageName(declaringClass));
 }

 scanner.addExcludeFilter(new AbstractTypeHierarchyTraversingFilter(false, false) {
  @Override
  protected boolean matchClassName(String className) {
   return declaringClass.equals(className);
  }
 });

 // 上面基本上都是解析配置的一些东西,过滤器啊,LazyInit等等,到这一步才是真正的扫描
 // StringUtils.toStringArray(basePackages) 获取包名
 return scanner.doScan(StringUtils.toStringArray(basePackages));
}

真正的扫描,做的事情可谓多得多

protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
 Assert.notEmpty(basePackages, "At least one base package must be specified");
 Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
 for (String basePackage : basePackages) {
  // 这里又是一个大的模块了,也就是读取ClassPath下所有的文件
  Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  // 拿到扫描到的 BeanDefinition 注册到工厂中
  for (BeanDefinition candidate : candidates) {
   ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
   candidate.setScope(scopeMetadata.getScopeName());
   String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
   if (candidate instanceof AbstractBeanDefinition) {
    postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
   }
   if (candidate instanceof AnnotatedBeanDefinition) {
    AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
   }
   if (checkCandidate(beanName, candidate)) {
    BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
    definitionHolder =
      AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
    beanDefinitions.add(definitionHolder);
    registerBeanDefinition(definitionHolder, this.registry);
   }
  }
 }
 return beanDefinitions;
}

扫描:

public Set<BeanDefinition> findCandidateComponents(String basePackage) {
 if (this.componentsIndex != null && indexSupportsIncludeFilters()) {
  return addCandidateComponentsFromIndex(this.componentsIndex, basePackage);
 }
 else {
  return scanCandidateComponents(basePackage);
 }
}
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();
  // 拿到包下两个文件的 Resource 实例
  for (Resource resource : resources) {
   if (traceEnabled) {
    logger.trace("Scanning " + resource);
   }
   if (resource.isReadable()) {
    try {
     MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);
     if (isCandidateComponent(metadataReader)) {
      ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
      sbd.setResource(resource);
      sbd.setSource(resource);
      // 符合某种条件,暂且就当BF中还没存在这个Bean吧
      if (isCandidateComponent(sbd)) {
       if (debugEnabled) {
        logger.debug("Identified candidate component class: " + resource);
       }
       // 添加
       candidates.add(sbd);
      }
      else {
       if (debugEnabled) {
        logger.debug("Ignored because not a concrete top-level class: " + resource);
       }
      }
     }
     else {
      if (traceEnabled) {
       logger.trace("Ignored because not matching any filter: " + resource);
      }
     }
    }
    catch (Throwable ex) {
     throw new BeanDefinitionStoreException(
       "Failed to read candidate component class: " + resource, ex);
    }
   }
   else {
    if (traceEnabled) {
     logger.trace("Ignored because not readable: " + resource);
    }
   }
  }
 }
 catch (IOException ex) {
  throw new BeanDefinitionStoreException("I/O failure during classpath scanning", ex);
 }
 // 返回
 return candidates;
}

OK,扫描完成,后在 finishBeanFactoryInitialization 进行初始化就可以了

结束