由于簡書的markdown不支持目錄結構,更好的閱讀體驗可以查看對應的個人blog: https://buaazhangyk.github.io/2018/07/02/spring-boot-1/
0. 前言
開始之前肌蜻,舉一個使用spring boot啟動web應用的代碼示例:
@SpringBootApplication
public class SpringbootPracticeApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootPracticeApplication.class, args);
}
}
通過上述代碼可以看到蒋搜,Spring Boot 中應用啟動的核心入口在SpringApplication
這個類中完成豆挽。繼續(xù)查看在SpringApplication
的run
方法內(nèi)部執(zhí)行中券盅,主要分為兩步:1.初始化創(chuàng)建一個SpringApplication
,2.然后執(zhí)行run(String... args)
方法對Application進行啟動娘侍。
public staticConfigurableApplicationContext run(Class[] primarySources, String[] args) {
return newSpringApplication(primarySources).run(args);
}
由此憾筏,本文對spring boot啟動過程的分析也會從這兩部分進行展開花鹅。1)SpringApplication的初始化部分刨肃; 2)SpringApplication的run執(zhí)行部分。
1. SpringApplication的初始化
1.1 SpringApplication類私有變量
下邊的圖簡單描述了SpringApplication類所包含的一些私有變量黄痪。后續(xù)會結合類的構造函數(shù)來分析其中的重要私有變量及其左右锻狗。
1.2 SpringApplication構造函數(shù)
結合Class的構造函數(shù)來重點看一下下面幾個變量的作用以及如何進行初始化的轻纪。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
1.2.1 ResourceLoader
表示Spring中用來加載資源的資源加載器刻帚。
1.2.2 webApplicationType
代表這個SpringApplication的類型崇众,主要包括三個類型:NONE / SERVLET / REACTIVE
NONE: The application should not run as a web application and should not start an embedded web server.
SERVLET: The application should run as a servlet-based web application and should start anembedded servlet
web server.
REACTIVE: The application should run as a reactive web application and should start anembedded reactive web server.
SpringBoot是怎么知道究竟是那種類型的ApplicationType的呢顷歌?實現(xiàn)的代碼在方法deduceWebApplicationType()
中。
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
在這段代碼里一個核心的方法調(diào)用是 ClassUtils.isPresent(String className,@NullableClassLoader classLoader)
,
這個方法是判斷參數(shù)中的className是否存在并可以加載成功芹扭。由此可見WebApplicationType類型的判斷取決于引入的jar包舱卡。
其中,
REACTIVE_WEB_ENVIRONMENT_CLASS 對應的類為 org.springframework.web.reactive
.DispatcherHandler队萤, 對應的package是spring-webflux
MVC_WEB_ENVIRONMENT_CLASS對應的類為org.springframework.web.servlet.DispatcherServlet 要尔, 對應的package是 spring-webmvc
WEB_ENVIRONMENT_CLASSES 對應的類為{"javax.servlet.Servlet","org.springframework.web.context
.ConfigurableWebApplicationContext” }赵辕,對應的package是servlet-api和spring-web
1.2.3 ApplicationContextInitializer
用來在對ApplicationContext進行refresh操作之前對Application context進行一些初始化操作。
Callback interface for initializing a Spring {@ConfigurableApplicationContext} prior to being
{@ConfigurableApplicationContext#refresh()} refreshed.
Typically used within web applications that require some programmatic initialization of the application context.
For example, registering property sources or activating profiles against the
{@ConfigurableApplicationContext#getEnvironment()}
context's environment. See {@ContextLoader} and {@FrameworkServlet} support for declaring a "contextInitializerClasses" context-param and init-param, respectively.
通過查看代碼熬词,我們可以看到ApplicationContextInitializer的獲取是通過調(diào)用 getSpringFactoriesInstances(Class<T> type)
方法得到的互拾,這個方法實際是去尋找指定Class類型的類并將其實例化返回颜矿。那具體是從哪里找呢骑疆? 會在后邊的小節(jié)單獨分析一下
1.2.4 ApplicationListener
基于觀察者模式的Application的事件監(jiān)聽器替废。將ApplicationListener注冊到ApplicationContext中椎镣,當有對應事件發(fā)生時,監(jiān)聽器會被調(diào)用
Interface to be implemented by application event listeners.
Based on the standard {@java.util.EventListener} interface for the Observer design pattern.
As of Spring 3.0, an ApplicationListener can generically declare the event type that it is interested in. When
registered with a Spring ApplicationContext, events will be filtered accordingly, with the listener getting invoked for matching event objects only.
與獲取ApplicationContextInitializer的過程一直冷守, ApplicationListener的獲取也是通過調(diào)用getSpringFactoriesInstances(Class<T> type)
實現(xiàn)拍摇。
1.2.5 mainApplicationClass
啟動應用程序的main class.通過分析當前的程序堆棧信息獲取亮钦。
private Class<?> deduceMainApplicationClass() {
try {
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
1.2.6 關于getSpringFactoriesInstances
2.SpringApplication的run
2.1 整體流程
run方法是SpringApplication的核心方法蜂莉,在這個方法內(nèi)部完成了 系統(tǒng)屬性的注入,Runner的執(zhí)行堪唐,
創(chuàng)建巡语、準備以及refresh整個ApplicationContext 核心流程。大致可以將整個run方法歸納分解成6個步驟淮菠。
[圖片上傳失敗...(image-4fd544-1531105646640)]
2.2 細節(jié)分析
下邊對這六個步驟進行詳細的分析和解讀男公。
2.2.1 SpringApplicationRunListeners
SpringApplicationRunListener是事件監(jiān)聽器,用來監(jiān)聽SpringApplication
的啟動過程合陵,監(jiān)聽到事件發(fā)生時進行一些回調(diào)操作枢赔。通過下邊的代碼可以看到獲取的核心方法是getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)
完成的
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
2.2.2 Prepare Environment
配置Application的環(huán)境,主要是一些property拥知,這些屬性會在創(chuàng)建ApplicationContext以及Referesh的時候起到左右踏拜。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
核心流程:
創(chuàng)建一個ConfigurableEnvironment
配置ConfigurableEnvironment,主要配置PropertySource(包括defaultProperties和addCommandLineProperties)和Profile姻锁。
將environment綁定至SpringApplication
Attach一個ConfigurationPropertySource至environment.
2.2.3 Create ApplicationContext
根據(jù)this.webApplicationType的類型來創(chuàng)建不同的ConfigurableApplicationContext。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
contextClass = Class.forName(DEFAULT_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);
}
2.2.4 Prepare Context
在 prepareContext的過程中,首先會運行一些初始化的流程笋妥,然后會注冊一些spring boot啟動所需要的bean挽鞠,加載一些初始的beans。
//1. Apply any relevant post processing the ApplicationContext
postProcessApplicationContext(context);
//2. Apply any {@linkApplicationContextInitializer}s to the context before it isrefreshed.
applyInitializers(context);
//3. Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
//4. Load beans into the application context.
Set sources = getAllSources();
load(context, sources.toArray(newObject[0]));
2.2.5 Refresh Context
對前邊兩步中創(chuàng)建和準備好的application context執(zhí)行refresh操作,這一部分的具體實現(xiàn)在spring-context包中潦蝇。本文不再具體分析攘乒。
2.2.6 Call Runner
主要是調(diào)用一下設置的一些ApplicationRunner和CommandLineRunner。
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
3 總結
Spring boot實際上是對原有Spring Application啟動方式的一種革命。
在傳統(tǒng)的Spring Application中爽雄,程序啟動的方式是傳統(tǒng)的web容器(如tomcat、jetty)為入口乘盖,spring application作為webAppContext插入到web容器中侧漓。而在spring
boot的方式中,完全是以spring application為主纵揍,傳統(tǒng)的web容器作為插件嵌入到spring application中。