SpringBoot启动流程的源码分析

前言

Spring Boot 版本 2.1.7.RELEASE
本文大致跟踪了一遍Spring Boot的启动流程。
请边debug源代码边看本文,因为很多变量细节在debug时才能清晰的观察到,而本文是无法全部涵盖所有细节的。
文中如有错误遗漏,请留言指正。

相关文章

SpringBoot中Tomcat的源码分析

正文

程序入口

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

SpringApplication.run()函数内部

return new SpringApplication(primarySources).run(args); //primarySources is XXXApplication.class

1. SpringApplication的构造函数内部

this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();

1.1 WebApplicationType.deduceFromClasspath() 根据org.springframework.web.reactive.DispatcherHandler javax.servlet.Servlet org.springframework.web.context.ConfigurableWebApplicationContext等类是否存在来推断服务器类型。类型包括WebApplicationType.REACTIVE WebApplicationType.SERVLET WebApplicationType.NONE

1.2 setInitializers()setListeners()函数为SpringApplication的成员变量initializerslisteners赋值。二者均使用getSpringFactoriesInstances(XXX.class)方法获取参数。
1.2.1 getSpringFactoriesInstances(XXX.class)方法中,参数XXX.class是一个interface。此方法首先会根据接口类的类名,获取其对应的多个实现类,然后实例化这些实现类,最后返回这些实例。这些实现类就是SpringFactories,是各个jar包中的factory。
getSpringFactoriesInstances()方法内部通过SpringFactoriesLoader.loadFactoryNames()方法加载。方法内部会在classpath下寻找路径为META-INF/spring.factories的所有文件,此文件的内容格式为properties,文件中的每一行记录一条interface及其对应的多个实现类。每行的内容形如
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
根据所有的spring.factories文件,可以构建出一个mapMultiValueMap,其中key为interface,value为其对应的多个实现类组成的list。这个map会存到Map> cache中,方便未来多次查找。
spring.factories的加载类似于工具类,没有确切的加载时机。随时用到随时加载,只是首次加载时,扫描到的spring.factories全部放入缓存,后续读取缓存。

1.2 续
通过getSpringFactoriesInstances()方法的搜索, SpringApplication实例的成员变量得到了赋值。initializers类型为List,包含了ApplicationContextInitializer.class的多个实现类;listeners类型为List,包含了ApplicationListener.class的多个实现类。

2 SpringApplication结束实例化后,会调用成员方法run()run()中有一些关键步骤
2.1 SpringApplicationRunListeners listeners = getRunListeners(args);
2.1.1 此方法首先使用 1.2.1 中提到的getSpringFactoriesInstances(XXX.class)方法搜索SpringApplicationRunListener.class的实现类。
SpringApplicationRunListener interface 定义了SpringApplication.run()运行过程中会抛出的事件,(这个接口类的命名也可以看出SpringApplication - Run - Listener)。

public interface SpringApplicationRunListener {
    void starting();
    void environmentPrepared(ConfigurableEnvironment environment);
    void contextPrepared(ConfigurableApplicationContext context);
    void contextLoaded(ConfigurableApplicationContext context);
    void started(ConfigurableApplicationContext context);
    void running(ConfigurableApplicationContext context);
    void failed(ConfigurableApplicationContext context, Throwable exception);
}

这些事件都是SpringApplicationEvent类的子类,后文将会提到这些事件。
SpringApplicationRunListener接口的实现类SpringApplicationRunListener将会被搜索出来并实例化。
SpringApplicationRunListener类内部实例化了一个ApplicationEventMulticaster成员变量,即SimpleApplicationEventMulticaster类。将来这个multicaster会用来抛出各个事件。将这个multicaster实例化完成后,又将SpringApplication.listeners填入到这个multicaster内部,作为其初始值,代码如下。

        this.initialMulticaster = new SimpleApplicationEventMulticaster();
        for (ApplicationListener listener : application.getListeners()) {
            this.initialMulticaster.addApplicationListener(listener);
        }

也就是说META-INF/spring.factories文件中定义的所有ApplicationListener.class的实现类都会监听SpringApplicationEvent事件。
2.1.2 最终getRunListeners(args)方法会将多个SpringApplicationRunListener接口的实现类封装成SpringApplicationRunListeners,然后返回。

2.2 listeners.starting();
SpringApplicationRunListener调用starting()方法,抛出ApplicationStartingEvent事件。
2.2.1 SimpleApplicationEventMulticaster类负责抛出事件。
2.2.1.1 SimpleApplicationEventMulticaster会首先调用getApplicationListeners()方法获取监听事件的所有listener。由于SimpleApplicationEventMulticaster类的初始值listener包括了SpringApplication.listeners(见2.1.1的末尾),所以这些listener会收到所有SpringApplicationEvent事件。
getApplicationListeners()中会使用AbstractApplicationEventMulticaster.supportsEvent()方法来筛选监听某事件的listener。
筛选出的listener会放入multicaster的retrieverCache中,以备将来再次查询。
2.2.1.2 SimpleApplicationEventMulticaster拿到所有的listener后,对每一个listener调用invokeListener(listener, event);。各个listener会通过ApplicationListener.onApplicationEvent(E event)方法实现自己的事件响应逻辑。

2.3 ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); 此方法执行了以下操作。
2.3.1 ConfigurableEnvironment environment = getOrCreateEnvironment();
此方法根据 1.1 中推断出的服务器类型,实例化ConfigurableEnvironment类。

switch (this.webApplicationType) {
        case SERVLET:
            return new StandardServletEnvironment();
        case REACTIVE:
            return new StandardReactiveWebEnvironment();
        default:
            return new StandardEnvironment();
        }

2.3.2 configureEnvironment(environment, applicationArguments.getSourceArgs());
本文以StandardServletEnvironment为例。configureEnvironment()包含以下步骤。
首先,

ConversionService conversionService = ApplicationConversionService.getSharedInstance();
environment.setConversionService((ConfigurableConversionService) conversionService);

为environment设置了ApplicationConversionService实例,用于一些数据格式的转换。
2.3.2.1 configurePropertySources(environment, args);
为environment配置Property源。
Property源按顺序为servletConfigInitParams servletContextInitParams systemProperties systemEnvironment(这些值是String,来源于多个类的静态变量)。其实Property源在environment的构造函数中就初始化了一部分。具体见各Environment类的构造函数代码。
systemProperties内的数据来源于System.getProperties();
systemEnvironment内的数据来源于System.getenv();
2.3.2.2 configureProfiles(environment, args);
从上一步 2.3.2.1 配置的各个Property源中寻找当前激活的Profile
property key 为"spring.profiles.active"
2.3.2.1中的数据源中没有"spring.profiles.active"的话,本步骤结束后,activeProfiles依旧为空。
2.3.3 listeners.environmentPrepared(environment);
发布ApplicationEnvironmentPreparedEvent事件
2.3.3.1 BootstrapApplicationListener会响应ApplicationEnvironmentPreparedEvent事件。
此listener会new StandardEnvironment(),与主流程的StandardServletEnvironment相互独立。在这个独立的环境下,通过硬编码的方式加载bootstrap配置文件(如:bootstrap.yml)。
相关代码通过这个字段可查到。org.springframework.cloud.bootstrap.BootstrapApplicationListener#BOOTSTRAP_PROPERTY_SOURCE_NAME = "bootstrap"
org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext方法中,通过硬编码的方式增加spring.config.name = bootstrap配置。
然后在加载配置文件过程中的org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchNames方法通过条件判断,选择加载bootstrap配置。下一节2.3.3.2中会在这个方法的判断中,选择加载application配置。
2.3.3.2 ConfigFileApplicationListener会响应ApplicationEnvironmentPreparedEvent事件。
此listener会在一些路径下寻找active profile,并加载profile内配置的properties,执行过程中的一些细节如下。

  • 搜索路径包括"classpath:/,classpath:/config/,file:./,file:./config/"。
  • 通过org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#loadDocuments()方法加载配置文件。关键代码是
    List> loaded = loader.load(name, resource);
    配置文件路径名举例"classpath:/application.yml" "classpath:/application-dev.yml"
  • 加载好的配置文件会被封装成PropertySources,并存储在org.springframework.boot.context.config.ConfigFileApplicationListener.Loader.loaded成员变量中。

2.4 Banner printedBanner = printBanner(environment);
打印Banner,就是在控制台里打印那个SpringBoot图形。

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.7.RELEASE)

2.5 context = createApplicationContext();
根据webApplicationType实例化ConfigurableApplicationContext
如,SERVLET类型对应的class是org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext

2.6 prepareContext(context, environment, listeners, applicationArguments, printedBanner);
以下是方法内的一些关键步骤。
2.6.1 applyInitializers(context);
使用SpringApplication.initializers内的各个initializer(见1.2),对context进行初始化。
2.6.2 listeners.contextPrepared(context);
发布ApplicationContextInitializedEvent事件
2.6.3 load(context, sources.toArray(new Object[0]));
具体执行的操作如下。
2.6.3.1 BeanDefinitionLoader loader = createBeanDefinitionLoader(getBeanDefinitionRegistry(context), sources);
BeanDefinitionLoader的作用是可以从XML和java注解中加载bean definition。
这行代码里的嵌套调用方法getBeanDefinitionRegistry(context)值得说一下。
2.6.3.1.1 getBeanDefinitionRegistry(context)
这里的context一般都是GenericApplicationContext的子类。
GenericApplicationContext类中有一个成员变量

private final DefaultListableBeanFactory beanFactory;

GenericApplicationContext类和DefaultListableBeanFactory类都实现了BeanDefinitionRegistry接口。GenericApplicationContext类其实是DefaultListableBeanFactory beanFactory的代理,它将所有对
BeanDefinitionRegistry接口的调用都交给了内部成员变量DefaultListableBeanFactory beanFactory去处理。
所以getBeanDefinitionRegistry(context)方法的调用一般得到的还是context自身。
2.6.3.2 loader.load();
将程序入口处的XXXpplication.class封装成BeanDefinition,并将其注册到context中(因为context实现了BeanDefinitionRegistry接口)。
2.6.4 listeners.contextLoaded(context);
SpringApplication.listenerslist中的listener全部加入到context中。
发布ApplicationPreparedEvent事件。
listener会在这个时候向context中加入一些BeanFactoryPostProcessor

2.7 refreshContext(context);
这里将会执行很多任务。
首先是调用AbstractApplicationContext.refresh()方法,此方法中一些关键步骤如下。
2.7.1 prepareRefresh();
设置AbstractApplicationContext为active

this.startupDate = System.currentTimeMillis();
this.closed.set(false);
this.active.set(true);

2.7.2 prepareBeanFactory(beanFactory);
为beanFactory设置一些值,具体见源码。
2.7.3 invokeBeanFactoryPostProcessors(beanFactory);
其中包括ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
正如其注释所言,ConfigurationClassPostProcessor会在此时扫描并注册BeanDefinition。

Derive further bean definitions from the configuration classes in the registry.

其中的核心方法为ConfigurationClassParser.parse()

2.7.3.1
由于入口类使用@SpringBootApplication注解,该注解包含了@ComponentScan
ConfigurationClassParser.parse()方法中首先会调用一次
org.springframework.context.annotation.ConfigurationClassParser.doProcessConfigurationClass()方法。由于入口类作为一个Bean,且被@ComponentScan注解,所以会自动扫描一下所有的java类来寻找Bean,但此次扫描的basePackages会使用main函数所在类的package。
例如com.demo.SomeTestApplication,则只会扫描com.demo下的bean。
2.7.3.2
ConfigurationClassParser.parse()方法的末尾处this.deferredImportSelectorHandler.process()会进行@Import注解的处理。
由于@SpringBootApplication注解包含了@EnableAutoConfiguration。而@EnableAutoConfiguration又包含了@Import(AutoConfigurationImportSelector.class)
所以会使用AutoConfigurationImportSelector.getCandidateConfigurations()方法对被@EnableAutoConfiguration注解的类进行加载。而获取这些被@EnableAutoConfiguration注解的类的方式是使用1.2.1中提到的SpringFactoriesLoader.loadFactoryNames()方法。即在META-INF/spring.factories文件中,将这些类配置为org.springframework.boot.autoconfigure.EnableAutoConfiguration的对应值,才能被识别。
这些被识别出的class会作为ConfigurationClass放入ConfigurationClassParser.configurationClasses这一map中。
被引入的ConfigurationClass也可以通过被@Import(XXXXImportSelector.class)注解的方式继续import其他ConfigurationClass

2.7.3 续
ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry方法的后续,会通过
Set configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
取出 2.7.3.2 中收集到的ConfigurationClass,使用
ConfigurationClassBeanDefinitionReader.loadBeanDefinitions()方法加载所有ConfigurationClass中配置的Bean。

2.7.4 onRefresh();, 此函数只做一件重要的事,就是createWebServer();首先在beanFactory寻找并实例化ServletWebServerFactory对应的bean,默认找到bean是TomcatServletWebServerFactory`。

注:
spring-boot-dependencies.pom文件中默认定义了依赖tomcat-embed-core.jar包,没有依赖其他的web容器jar包。在 2.7.3 中扫描BeanDefinition时,发现了ServletWebServerFactoryConfiguration配置类。在该配置类中定义了多个WebServer的bean方法,包括Tomcat,Jetty和Undertow。但是因为只有Tomcat.class等tomcat相关类存在,所以只有tomcatServletWebServerFactory()bean method生效,默认实例化的便是TomcatServletWebServerFactory
Tomcat相关流程的源码分析在本文开头的相关文章中介绍,本文不再赘述。

this.webServer = factory.getWebServer(getSelfInitializer()); 生成tomcat实例并启动tomcat。
2.7.5 初始化剩余的(non-lazy-init)单例的bean,一般controller啥的就会在这里被实例化

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

2.7.6 finishRefresh();
2.7.6.1 publishEvent(new ContextRefreshedEvent(this));
发布ContextRefreshedEvent事件
2.7.6.2 WebServer webServer = startWebServer();
启动TomcatWebServer,与2.7.4中的启动tomcat时不同的。
2.7.6.3 publishEvent(new ServletWebServerInitializedEvent(webServer, this));
发布ServletWebServerInitializedEvent事件

2.7 续 最后回到SpringApplication.refreshContext(context);方法内部
registerShutdownHook

2.8 listeners.started(context);
发布ApplicationStartedEvent事件

2.9 listeners.running(context);
发布ApplicationReadyEvent事件

3 至此,SpringApplication.run()函数全部执行完毕,也就意味着main()函数执行完毕。main线程结束,java虚拟机启动一个DestroyJavaVM线程,并等待其他的非daemon线程执行结束。

附:SpringApplicationEvent各个事件的抛出位置

事件类型(SpringApplicationEvent的子类) SpringApplicationRunListener接口的调用方法 本文的章节号
ApplicationStartingEvent starting() 2.2
ApplicationEnvironmentPreparedEvent environmentPrepared() 2.3.3
ApplicationContextInitializedEvent contextPrepared() 2.6.2
ApplicationPreparedEvent contextLoaded() 2.6.4
ApplicationStartedEvent started() 2.8
ApplicationReadyEvent running() 2.9 ·

你可能感兴趣的:(SpringBoot启动流程的源码分析)