Spring Boot启动流程&自动装配

868 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情

前言

所有的框架的目标必定包含让开发者更专注于业务,Spring Boot也一样。最耳熟能详的就是“约定大于配置”,通过“约定”让开发人员开箱即用,能够很快的进行体验,只需要很少的“需求”配置。

那么Spring Boot中有哪些约定呢?

  • Maven的目录结构。默认resouces文件,存放资源配置文件。src-main-resources,src-main-java。默认的编译器生成的类都在target文件夹下面。
  • Spring boot默认的配置文件必须是application.命名的yml文件或者properties文件,且唯一。
  • application.yml中默认属性。如数据库连接信息必须是以spring:datasource:为前缀;多环境配置。该属性可以根据运行环境自动读取不同的配置文件;端口号、请求路径等。

下面我们主要开了解下Spring Boot的启动流程和自动装配。笔者的版本是

启动流程

从main方法中开始

package com.study.spring;

import lombok.SneakyThrows;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan(basePackages = {"com.study.spring.dao"})
public class Application {
    @SneakyThrows
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

进入SpringApplication.run。

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);
}

可以看到先new SpringApplication然后再进行run。我们分这两部分进行阅读。

创建SpringApplication

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
   // 没有指定就用默认的
   this.resourceLoader = resourceLoader;
   Assert.notNull(primarySources, "PrimarySources must not be null");
   // 这里primarySources为com.study.spring.Application
   this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
   // 这里为SERVLET(基于servlet的web程序,需要启动内嵌的servlet web容器,比如tomact)。
   // 其他知道还有NONE(什么都没有,正常流程,不额外的启动web容器,比如Tomact)、REACTIVE(基于reactive的web程序,需要启动内嵌reactive web容器)。
   this.webApplicationType = WebApplicationType.deduceFromClasspath();
   this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
   // 设置ApplicationContextInitializer,用户IOC容器初始化之后初始化一些组件,后面会单独介绍。
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   // 监听器ApplicationListener,用于围绕IOC容器初始化前中后去执行相关程序。
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   // 决定主启动类,这里为com.study.spring.Application
   this.mainApplicationClass = deduceMainApplicationClass();
}

大致如下图: image.png

ApplicationContextInitializer

这个类的主要目的就是在ConfigurableApplicationContext类型(或者子类型)的ApplicationContext进行refresh之前。用来对应用上下文进行初始化的。

主要来源为各个依赖包的META-INF/spring.factories的org.springframework.context.ApplicationContextInitializer=,使用的是SPI机制(通过配置寻找具体的实现,接口编程+策略模式+配置文件)。

具体在run方法中使用到了,在refresh之前用来初始化context,使用的是责任链模式。

image.png

image.png

image.png 可以看到这里在context中增加了BeanFactoryPostProcessor。

ApplicationListener

ApplicationListener是Spring提供的接口,作用是在Web服务器启动时去加载某些程序。由应用程序事件侦听器实现的接口。观察者设计模式基于标准java.util.EventListener。主要来源为各个依赖包的META-INF/spring.factories,还可以直接实现ApplicationListener。

image.png 上图就是自己实现的,不过是在refresh里面刷新完后执行的。这里是同步执行的,所以笔者使用了线程池进行了异步,不影响启动。

run方法

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
   configureHeadlessProperty();
   //获取运行过程监听器
   SpringApplicationRunListeners listeners = getRunListeners(args);
   //启动运行过程监听器,执行starting
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      //构建环境
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      //这个就是springboot启动的时候打印的字符串,比如“永无bug”
      Banner printedBanner = printBanner(environment);
      //创建容器
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      //IOC容器的前置处理器,这里会执行在喉间SpringApplication中设置的ApplicationContextInitializer
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      //刷新容器
      refreshContext(context);
      //IOC容器的后处理器
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      //发出结束执行事件
      listeners.started(context);
      //执行Runners
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

大致执行入下图:

image.png

前面创建SpringApplication时设置的ApplicationListener会根据SpringApplicationRunListeners执行不同的方法触发不同的事件,比如listeners.starting(bootstrapContext,this.mainApplicationClass);会触发类型为org.springframework.boot.context.event.ApplicationStartingEvent事件的监听器进行执行。

image.png

image.png

image.png 最后可以看到使用了ApplicationEventMulticaster执行所有该事件类型的listener。同时可以看到这里使用了线程池异步执行。

自动装配

获取所有EnableAutoConfiguration

首先自动转配的实现来自于META-INF/spring.factories中的org.springframework.boot.autoconfigure.EnableAutoConfiguration。在创建SpringApplication是设置ApplicationContextInitializer的时候是第一次loader spring.factories之后会cache住。

image.png

注册EnableAutoConfiguration

通过@EnableAutoConfiguration@Import(AutoConfigurationImportSelector.class)找到AutoConfigurationImportSelector,在AutoConfigurationImportSelector#getCandidateConfigurations中打个断点,就可以看到了:

image.png

这里的ConfiguractionClassPostProcessor是一个BeanFactoryPostProcessor用来对所有自动装配的Configuration的bean定义进行注册,方便后面进行自动装配。

image.png

总结

在创建SpringApplication时对ApplicationContextInitializer进行设置是扫描了所有META-INF/spring.factories进行cache住了org.springframework.boot.autoconfigure.EnableAutoConfiguration,后面通过reflush中的invokeBeanFactoryPostProcessors(beanFactory)调用ConfiguractionClassPostProcessor进而调用AutoConfiguractionImportSelector拿到所有的autoconfiguration注册了bean定义进而实现自动装配。