前言
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
的成员变量initializers
和listeners
赋值。二者均使用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
中,方便未来多次查找。
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.listeners
list中的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
取出 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 | · |