Spring Boot源码剖析

一、概述

Java程序员们对Spring Boot这个框架再熟悉不过了,它最大的特点就是自动配置、内嵌容器、可插拔式插件等等……这些特点使得Spring Boot能够做到快速开发,大大提高开发者的效率,今天这篇我来分享一下Spring Boot的启动流程。


二、Spring Boot核心注解

上篇我们自定义了一个spring-boot-starter,用大白话说这就是一个“自定义的可插拔式spring boot插件”,那么spring boot项目启动时是如何自动加载这些“可插拔式插件”的呢?接下来我们就先简单描述一下它的启动流程。

首先Spring Boot项目的启动类上都有一个@SpringBootApplication注解,该注解是一个组合注解,如下图所示

Spring Boot源码剖析

@Target、@Retention、@Documented这三个是java中的元注解,不知道的朋友可以自行查阅资料。

@Inherited的作用是传递继承关系,比如子类要继承父类的注解,那么父类上就需要标记该注解。

@SpringBootConfiguration,该注解里面有一个@Configuration注解,@Configuration是spring提供的注解,表示该类是可以被扫描的配置类。

@EnableAutoConfiguration该注解同样也是一个组合注解,且该注解就是Spring Boot的核心注解,如下图所示

Spring Boot源码剖析

该注解下有两个比较重要的注解@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class),其中@AutoConfigurationPackage注解下面有一行@Import(AutoConfigurationPackages.Registrar.class)注解,如下图所示

Spring Boot源码剖析

该注解通过@Import导入了一个AutoConfigurationPackages.Registrar.class类,该类的作用是扫描主程序类所在包及其子类包下的所有组件,所以spring boot项目的主启动类的位置要在所有子类的根目录位置。

@Import(AutoConfigurationImportSelector.class)注解导入了一个AutoConfigurationImportSelector.class类,该类的作用主要用来自动装配“可插拔式插件”的

@ComponentScan()注解的作用是定义组件扫描的范围。

以上Spring Boot的几个核心注解先简单介绍到这里。


三、Spring Boot启动流程

接下来我将通过debug模式来探究下spring boot项目的启动流程。用来debug的项目我们选用上篇中的gzh-springboot项目(版本2.2.1.RELEASE)。

如图所示我们先将代码定位到项目的主启动类,将断点打在第15行

Spring Boot源码剖析

断点依次经过一下代码

 // 先进入run方法
 public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
    return run(new Class<?>[] { primarySource }, args);
  }
  // 再j进入run的重载方法
  public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    return new SpringApplication(primarySources).run(args);
  }
  // 实例化SpringApplication对象
  public SpringApplication(Class<?>... primarySources) {
    this(null, primarySources);
  }
  // 实例化SpringApplication对象
  public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 判断当前webApplicationType是何类型
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 初始化META-INF/spring.factries文件下的可用ApplicationContextInitializer类
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 设置META-INF/spring.factries文件下的可用监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 设置main主启动类
    this.mainApplicationClass = deduceMainApplicationClass();
  }

以上代码是初始化SpringApplication对象的主要流程,在该过程中,扫描到的ApplicationContextInitializer类将存放在initializers集合里面,扫描到的ApplicationListener类将存放在listeners集合里面,如下图所示

Spring Boot源码剖析

Spring Boot源码剖析

Spring Boot源码剖析

完成初始化之后,开始执行run方法,该方法代码如下

public ConfigurableApplicationContext run(String... args) {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    ConfigurableApplicationContext context = null;
    Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
    configureHeadlessProperty();
    // 第一步:获取并启动监听器
    SpringApplicationRunListeners listeners = getRunListeners(args);
    listeners.starting();
    try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      // 第二步:根据SpringApplicationRunListeners以及参数来准备环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
      configureIgnoreBeanInfo(environment);
      // 第三步:准备Banner打印器,就是项目启动时console上面显示的艺术字体
      Banner printedBanner = printBanner(environment);
      // 第四步:创建spring容器
      context = createApplicationContext();
      exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
          new Class[] { ConfigurableApplicationContext.class }, context);
      // 第五步:spring容器前置处理
      prepareContext(context, environment, listeners, applicationArguments, printedBanner);
      // 第六步:刷新容器
      refreshContext(context);
      // 第七步:spring后置处理
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
        new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, listeners);
      throw new IllegalStateException(ex);
    }
​
    try {
      listeners.running(context);
    }
    catch (Throwable ex) {
      handleRunFailure(context, ex, exceptionReporters, null);
      throw new IllegalStateException(ex);
    }
    return context;
  }

以上代码和spring的主要步骤很类似,我们这里就不过多赘述,直接将代码定位到前面分析的AutoConfigurationImportSelector.class将断点打在如下图所示的地方

Spring Boot源码剖析

代码执行到396行,如下所示

((AutoConfigurationImportSelector) deferredImportSelector)
          .getAutoConfigurationEntry(getAutoConfigurationMetadata(), annotationMetadata);

我们这边主要关注getAutoConfigurationEntry和getAutoConfigurationMetadata方法,先看getAutoConfigurationMetadata如下

    // 进入getAutoConfigurationMetadata方法
    private AutoConfigurationMetadata getAutoConfigurationMetadata() {
      if (this.autoConfigurationMetadata == null) {
        // 调用loadMetadata方法
        this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
      }
      return this.autoConfigurationMetadata;
    }  

    程序进入loadMetadata方法,如下图所示

Spring Boot源码剖析

从该图的信息中,我们可以看出程序加载了META-INF/spring-autoconfigure-metadata.properties文件,该文件内容如下

Spring Boot源码剖析

从debug中获得的信息来看,该文件的内容被加载到properties里面了。

看完getAutoConfigurationMetadata方法,我们再看getAutoConfigurationEntry方法,如下

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
      AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
      return EMPTY_ENTRY;
    }
    // 第一步:获取合适的AnnotationAttributes对象
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    // 第二步:让springFactoryLoader加载一些组件的名字
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    // 删除一些不需要的组件名称
    configurations.removeAll(exclusions);
    configurations = filter(configurations, autoConfigurationMetadata);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
  }

我们主要看第一步和第二步。

第一步debug进入代码如图所示

Spring Boot源码剖析

我们看到程序找的是org.springframework.boot.autoconfigure.EnableAutoConfiguration类,该类在我们上篇自定义spring-boot-starter中配置文件有定义,忘记的小伙伴可以去翻阅一下。

第二步关键代码如下

  protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 通过SpringFactoriesLoader加载一些组件名
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
        getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
        + "are using a custom packaging, make sure that file is correct.");
    return configurations;
  }
  
  public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
    String factoryTypeName = factoryType.getName();
    // 调用loadSPringFactories方法,如下图
    return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
  }

Spring Boot源码剖析

我们可以看到classLoader.getResources(FACTORIES_RESOURCE_LOCATION),加载的是META-INF/spring.factories文件,我们自定义“可插拔式插件”的时候,就是定义了这个文件。

看完AutoConfigurationImportSelector.class相关的代码,我们再看下AutoConfigurationPackages.Registrar.class类,如下图所示,debug进入以下代码

Spring Boot源码剖析

Spring Boot源码剖析

new PackageImport(metadata).getPackageName()运行的结果是主启动类所在的包名,获取到主启动类所在包之后,程序就会扫描该包下的所有组件,将其放进IOC容器里面,具体详情这里就不再赘述……

以上分析了spring boot自动装配相关的源码,有时间的朋友可以深度再探究其他部分的源码。


四、小结

通过以上分析,我们大致总结一下spring boot自动装配的原理:当spring boot项目启动时会通过SpringFactoriesLoader.loadFactoryNames加载META-INF/spring.factories配置文件并读取里面的内容,然后自动配置的时候获取org.springframework.boot.autoconfigure.EnableAutoConfiguration相关的类,将该类自动装配到spring boot项目里面,通过这样的方式实现自定义sprin-boot-starter的可插拔功能。

另外spring boot会扫描启动类所在包下的所有组件,需要注意的是,spring boot项目的主启动类要放在最外层,否则会导致有些组件无法被注入到容器里面。

以上就是今天的分享了……


感兴趣的读者朋友可以 关注本公众号,和我们一起学习探究。


Spring Boot源码剖析


本人因所学有限,如有错误之处,望请各位指正!

上一篇:Vim Tab to Space and other configurations


下一篇:这一次的SpringBoot启动解析,是因为一个面试题引起的