SpringBoot配置外部Tomcat項(xiàng)目啟動(dòng)流程源碼分析(一)

前言

SpringBoot應(yīng)用默認(rèn)以Jar包方式并且使用內(nèi)置Servlet容器(默認(rèn)Tomcat)炊汤,該種方式雖然簡(jiǎn)單但是默認(rèn)不支持JSP并且優(yōu)化容器比較復(fù)雜劈伴。故而我們可以使用習(xí)慣的外置Tomcat方式并將項(xiàng)目打War包。

【1】創(chuàng)建項(xiàng)目并打War包

① 同樣使用Spring Initializer方式創(chuàng)建項(xiàng)目

a9d884dd2b0c6e5ce20418044a4a9710.jpeg

② 打包方式選擇"war"

5997c452519013a4fb460227587569e3.jpeg

③ 選擇添加的模塊

8882759c018df2220748b987c6ff7efc.jpeg

④ 創(chuàng)建的項(xiàng)目圖示

041e3c894233a1d5f608f4600decbe5c.jpeg

有三個(gè)地方需要注意:

  • pom中打包方式已經(jīng)為war;
  • 對(duì)比默認(rèn)為jar的項(xiàng)目多了ServletInitializer類(lèi)镶柱;
  • 項(xiàng)目結(jié)構(gòu)沒(méi)有src/main/webapp旷档,且沒(méi)有WEB/INF web.xml。

ServletInitializer類(lèi)如下:

public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(SpringbootwebprojectApplication.class);
}
}

pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.web</groupId>
<artifactId>springbootwebproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>springbootwebproject</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--這里修改了內(nèi)置Tomcat的作用域-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

⑤ 補(bǔ)全項(xiàng)目結(jié)構(gòu)

第一種方式歇拆,手動(dòng)創(chuàng)建src/main/webapp鞋屈, WEB/INF以及web.xml。

第二種方式故觅,使用idea創(chuàng)建厂庇,步驟如下:

1.如下圖所示,點(diǎn)擊項(xiàng)目結(jié)構(gòu)圖標(biāo)

da44c9ad81d7b4f9e98a1f064c94358f.jpeg

2.創(chuàng)建src/main/webapp

13252064f8fac349d5570ba62b49c0fe.jpeg

3.創(chuàng)建web.xml

cfc979eeb6a27b5c3efe02f651cc7030.jpeg
835c404881f732e7045abcc95192abf7.jpeg

此時(shí)項(xiàng)目結(jié)構(gòu)圖如下:

71a1ad0e3eb6f3ebc604f28f873ff3fc.jpeg

【2】使用外部配置的Tomcat啟動(dòng)項(xiàng)目

① 點(diǎn)擊"Edit Configurations…"添加Tomcat输吏。

66c7a3c696938de648bef2da68837d92.jpeg

② 設(shè)置Tomcat权旷、JDK和端口

2c3084ecf925cffe27fc380214d02118.jpeg

③ 部署項(xiàng)目

d529764c18e406fbf217bcc1f0781dc2.jpeg
a5f986464d9113f3b27bc15a9dddf736.jpeg

④ 啟動(dòng)項(xiàng)目

1df28109ffdb0da9be5245d2aec88023.jpeg

此時(shí)如果webapp 下有index.html,index.jsp,則會(huì)默認(rèn)訪問(wèn)index.html。

如果只有index.jsp贯溅,則會(huì)訪問(wèn)index.jsp拄氯;如果webapp下無(wú)index.html或index.jsp躲查,則從靜態(tài)資源文件夾尋找index.html;如果靜態(tài)資源文件夾下找不到index.html且項(xiàng)目沒(méi)有對(duì)"/"進(jìn)行額外攔截處理译柏,則將會(huì)返回默認(rèn)錯(cuò)誤頁(yè)面镣煮。

index.html顯示如下圖:

754db2a7791bbcb7285df65ccecf9736.jpeg

【3】SpringBoot 使用外部Tomcat啟動(dòng)原理

① 首先看Servlet3.0中的規(guī)范

  • javax.servlet.ServletContainerInitializer(其是一個(gè)接口) 類(lèi)是通過(guò)JAR服務(wù)API查找的。對(duì)于每個(gè)應(yīng)用程序鄙麦,ServletContainerInitializer的一個(gè)實(shí)例是由容器在應(yīng)用程序啟動(dòng)時(shí)創(chuàng)建典唇。
  • 提供servletcontainerinitializer實(shí)現(xiàn)的框架必須將名為javax.servlet的文件捆綁到j(luò)ar文件的META-INF/services目錄中。根據(jù)JAR服務(wù)API胯府,找到指向ServletContainerInitializer的實(shí)現(xiàn)類(lèi)介衔。
  • 除了ServletContainerInitializer 之外,還有一個(gè)注解–@HandlesTypes盟劫。ServletContainerInitializer 實(shí)現(xiàn)上的handlesTypes注解用于尋找感興趣的類(lèi)–要么是@HandlesTypes注解指定的類(lèi)夜牡,要么是其子類(lèi)。
  • 不管元數(shù)據(jù)完成的設(shè)置如何侣签,都將應(yīng)用handlesTypes注解。
  • ServletContainerInitializer實(shí)例的onStartup 方法將在應(yīng)用程序啟動(dòng)時(shí)且任何servlet偵聽(tīng)器事件被激發(fā)之前被調(diào)用急迂。
  • ServletContainerInitializer 的onStartup 方法調(diào)用是伴隨著一組類(lèi)的(Set<Class<?>> webAppInitializerClasses)影所,這些類(lèi)要么是initializer的擴(kuò)展類(lèi),要么是添加了@HandlesTypes注解的類(lèi)僚碎。將會(huì)依次調(diào)用webAppInitializerClasses實(shí)例的onStartup方法猴娩。

總結(jié)以下幾點(diǎn):

1)服務(wù)器啟動(dòng)(web應(yīng)用啟動(dòng))會(huì)創(chuàng)建當(dāng)前web應(yīng)用里面每一個(gè)jar包里面
ServletContainerInitializer實(shí)例;

2)jar包的META-INF/services文件夾下勺阐,有一個(gè)名為
javax.servlet.ServletContainerInitializer的文件卷中,內(nèi)容就是ServletContainerInitializer的實(shí)現(xiàn)類(lèi)的全類(lèi)名;

如下圖所示:

a65a2ca3d9b8fda25860129a660d0fa4.jpeg

3)還可以使用@HandlesTypes渊抽,在應(yīng)用啟動(dòng)的時(shí)候加載我們感興趣的類(lèi)蟆豫;

4)容器啟動(dòng)過(guò)程中首先調(diào)用
ServletContainerInitializer 實(shí)例的onStartup方法。

ServletContainerInitializer 接口如下:

public interface ServletContainerInitializer {
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

② 步驟分析如下

第一步懒闷,Tomcat啟動(dòng)

fba5f2d4ce6f6c16e54d9d8c7c8455d9.jpeg

第二步十减,根據(jù)Servlet3.0規(guī)范,找到
ServletContainerInitializer 愤估,進(jìn)行實(shí)例化

jar包路徑:

org\springframework\spring-web\4.3.14.RELEASE\
spring-web-4.3.14.RELEASE.jar!\METAINF\services\
javax.servlet.ServletContainerInitializer:

Spring的web模塊里面有這個(gè)文件:

org.springframework.web.SpringServletContainerInitializer
f01fbf21f0f22428094cf36f00cd25ac.jpeg

第三步帮辟,創(chuàng)建實(shí)例

SpringServletContainerInitializer將@HandlesTypes(WebApplicationInitializer.class)標(biāo)注的所有這個(gè)類(lèi)型的類(lèi)都傳入到onStartup方法的Set集合,為這些WebApplicationInitializer類(lèi)型的類(lèi)創(chuàng)建實(shí)例并遍歷調(diào)用其onStartup方法玩焰。

SpringServletContainerInitializer 源碼如下(調(diào)用其onStartup方法):

//感興趣的類(lèi)為WebApplicationInitializer及其子類(lèi)
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//先調(diào)用onStartup方法由驹,會(huì)傳入一系列webAppInitializerClasses
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
//遍歷感興趣的類(lèi)
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
//判斷是不是接口,是不是抽象類(lèi)昔园,是不是該類(lèi)型
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//實(shí)例化每個(gè)initializer并添加到initializers中
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
//依次調(diào)用initializer的onStartup方法蔓榄。
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
3919d225abaeb4ebed863ac1f491d218.jpeg

如上所示闹炉,在
SpringServletContainerInitializer方法中又調(diào)用每一個(gè)initializer的onStartup方法。即先調(diào)用SpringServletContainerInitializer實(shí)例的onStartup方法润樱,在onStartup()方法內(nèi)部又遍歷每一個(gè)WebApplicationInitializer類(lèi)型的實(shí)例渣触,調(diào)用其onStartup()方法。

WebApplicationInitializer(Web應(yīng)用初始化器)是什么壹若?

在Servlet 3.0+環(huán)境中提供的一個(gè)接口嗅钻,以便編程式配置ServletContext而非傳統(tǒng)的xml配置。該接口的實(shí)例被
SpringServletContainerInitializer自動(dòng)檢測(cè)(@HandlesTypes(WebApplicationInitializer.class)這種方式)店展。而SpringServletContainerInitializer是Servlet 3.0+容器自動(dòng)引導(dǎo)的养篓。通過(guò)WebApplicationInitializer,以往在xml中配置的DispatcherServlet赂蕴、Filter等都可以通過(guò)代碼注入柳弄。你可以不用直接實(shí)現(xiàn)WebApplicationInitializer,而選擇繼承AbstractDispatcherServletInitializer概说。

WebApplicationInitializer類(lèi)型的類(lèi)如下圖:

b522e2809c52ec0f416a4b03ab3703b1[0].jpeg

可以看到碧注,將會(huì)創(chuàng)建我們的
com.web.ServletInitializer(繼承自SpringBootServletInitializer)實(shí)例,并調(diào)用onStartup方法糖赔。

第四步:我們的
SpringBootServletInitializer的實(shí)例(com.web.ServletInitializer)會(huì)被創(chuàng)建對(duì)象萍丐,并執(zhí)行onStartup方法(com.web.ServletInitializer繼承自SpringBootServletInitializer,故而會(huì)調(diào)用SpringBootServletInitializer的onStartup方法)

SpringBootServletInitializer源碼如下:

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// Logger initialization is deferred in case an ordered
// LogServletContextInitializer is being used
this.logger = LogFactory.getLog(getClass());
//創(chuàng)建WebApplicationContext
WebApplicationContext rootAppContext = createRootApplicationContext(
servletContext);
if (rootAppContext != null) {
//如果根容器不為null放典,則添加監(jiān)聽(tīng)--注意這里的ContextLoaderListener逝变,
//contextInitialized方法為空,因?yàn)槟J(rèn)application context已經(jīng)被初始化
servletContext.addListener(new ContextLoaderListener(rootAppContext) {
@Override
public void contextInitialized(ServletContextEvent event) {
// no-op because the application context is already initialized
}
});
}
else {
this.logger.debug("No ContextLoaderListener registered, as "
+ "createRootApplicationContext() did not "
+ "return an application context");
}
}

可以看到做了兩件事:創(chuàng)建RootAppContext 和為容器添加監(jiān)聽(tīng)奋构。

創(chuàng)建WebApplicationContext 源碼如下:

protected WebApplicationContext createRootApplicationContext(
ServletContext servletContext) {
//創(chuàng)建SpringApplicationBuilder --這一步很關(guān)鍵
SpringApplicationBuilder builder = createSpringApplicationBuilder();
//設(shè)置應(yīng)用主啟動(dòng)類(lèi)--本文這里為com.web.ServletInitializer
builder.main(getClass());

*/從servletContext中獲取servletContext.getAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE)作為parent壳影。第一次獲取肯定為null
*/
ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
if (parent != null) {
this.logger.info("Root context already created (using as parent).");
servletContext.setAttribute(
//以將ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE重置為null
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
//注冊(cè)一個(gè)新的ParentContextApplicationContextInitializer--包含parent
builder.initializers(new ParentContextApplicationContextInitializer(parent));
}
//注冊(cè)ServletContextApplicationContextInitializer--包含servletContext
builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));
//設(shè)置applicationContextClass為AnnotationConfigServletWebServerApplicationContext
builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
builder = configure(builder);
//添加監(jiān)聽(tīng)器
builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext));
//返回一個(gè)準(zhǔn)備好的SpringApplication ,準(zhǔn)備run-很關(guān)鍵
SpringApplication application = builder.build();
if (application.getAllSources().isEmpty() && AnnotationUtils
.findAnnotation(getClass(), Configuration.class) != null) {
application.addPrimarySources(Collections.singleton(getClass()));
}
Assert.state(!application.getAllSources().isEmpty(),
"No SpringApplication sources have been defined. Either override the "
+ "configure method or add an @Configuration annotation");
// Ensure error pages are registered
if (this.registerErrorPageFilter) {
application.addPrimarySources(
Collections.singleton(ErrorPageFilterConfiguration.class));
}
//啟動(dòng)應(yīng)用
return run(application);
}

【4】createRootApplicationContext詳細(xì)流程源碼分析

① createRootApplicationContext().createSpringApplicationBuilder()

跟蹤代碼到:

public SpringApplicationBuilder(Class<?>... sources) {
this.application = createSpringApplication(sources);
}

此時(shí)的Sources為空弥臼,繼續(xù)跟蹤代碼:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//web應(yīng)用類(lèi)型--Servlet
this.webApplicationType = deduceWebApplicationType();
獲取ApplicationContextInitializer宴咧,也是在這里開(kāi)始首次加載spring.factories文件
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
這里是第二次加載spring.factories文件
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
f87b1f1793863c8f19f1543784c7a851.jpeg

ApplicationContextInitializer是spring組件spring-context組件中的一個(gè)接口,主要是spring ioc容器刷新之前的一個(gè)回調(diào)接口醋火,用于處于自定義邏輯悠汽。

ApplicationContextInitializer(應(yīng)用上下文初始化器)是什么?


ConfigurableApplicationContext-Spring IOC容器稱(chēng)為“已經(jīng)被刷新”狀態(tài)前的一個(gè)回調(diào)接口去初始化ConfigurableApplicationContext芥驳。通常用于需要對(duì)應(yīng)用程序上下文進(jìn)行某些編程初始化的Web應(yīng)用程序中柿冲。例如,與ConfigurableApplicationContext#getEnvironment() 對(duì)比兆旬,注冊(cè)property sources或激活配置文件假抄。另外ApplicationContextInitializer(和子類(lèi))相關(guān)處理器實(shí)例被鼓勵(lì)使用去檢測(cè)org.springframework.core.Ordered接口是否被實(shí)現(xiàn)或是否存在org.springframework.core.annotation.Order注解,如果存在,則在調(diào)用之前對(duì)實(shí)例進(jìn)行相應(yīng)排序宿饱。

spring.factories文件中的實(shí)現(xiàn)類(lèi):

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
org.springframework.boot.diagnostics.FailureAnalyzers
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\
org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor,\
org.springframework.boot.env.SystemEnvironmentPropertySourceEnvironmentPostProcessor
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindValidationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.UnboundConfigurationPropertyFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyNameFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.InvalidConfigurationPropertyValueFailureAnalyzer
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter
ce89a96d69e103c302c88a495066a266.jpeg

設(shè)置WebApplicationType

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

這里主要是通過(guò)判斷REACTIVE相關(guān)的字節(jié)碼是否存在熏瞄,如果不存在,則web環(huán)境即為SERVLET類(lèi)型谬以。這里設(shè)置好web環(huán)境類(lèi)型强饮,在后面會(huì)根據(jù)類(lèi)型初始化對(duì)應(yīng)環(huán)境。

設(shè)置Initializer–
ApplicationContextInitializer類(lèi)型

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//線程上下文類(lèi)加載器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

這里ClassLoader 獲取的是線程上下文類(lèi)加載器为黎,這里使用的是Tomcat啟動(dòng):


ee2a435ad8c51cfc01418e2ef77ac441.jpeg

Set<String> names如下:

c170581f25f8be240095bcb33b3092e2.jpeg

獲取了6個(gè)instance:

7d808669524d126357b79cb65b59301d.jpeg

設(shè)置監(jiān)聽(tīng)–ApplicationListener類(lèi)型

此時(shí)的type為ApplicationListener邮丰,Set<String> names如下:

9632f6bdb8961eb9fab9724ec1dee6aa[0].jpeg

至此SpringApplicationBuilder創(chuàng)建完畢。

② 添加
ServletContextApplicationContextInitializer

builder.initializers(
new ServletContextApplicationContextInitializer(servletContext));

此時(shí)SpringApplication Initializers和Listener如下:

d7007f37955fdc17de8302d8948eb775.jpeg

③ 設(shè)置
application.setApplicationContextClass

7db2ab778132b6ae932b2edb0b1383bc[0].jpeg

④ builder = configure(builder);

此時(shí)調(diào)用我們的ServletInitializer的configure方法:

357f32e08e69d2d47d2cb0b425d02f89.jpeg
efe78c3d2ff751b15898ec389dc7602f.jpeg

⑤ SpringApplication application = builder.build()創(chuàng)建應(yīng)用

把我們的主類(lèi)添加到application 中:

27474d190ce9b6f2310f0165721724cb.jpeg

⑥ 將
ErrorPageFilterConfiguration添加到Set<Class<?>> primarySources

d96dbcb9c688ad0211c1a20c6a9d9bad.jpeg

接下來(lái)該run(application)了注意直到此時(shí)铭乾,我們讓沒(méi)有創(chuàng)建我們想要的容器剪廉,容器將會(huì)在run(application)中創(chuàng)建。

SpringApplication.run源碼如下所示:

/**
run Spring application炕檩,創(chuàng)建并刷新一個(gè)新的ApplicationContext
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
//簡(jiǎn)單的秒表斗蒋,允許對(duì)許多任務(wù)計(jì)時(shí),顯示每個(gè)指定任務(wù)的總運(yùn)行時(shí)間和運(yùn)行時(shí)間笛质。非線程安全
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
//異常報(bào)告集合
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//java.awt.headless是J2SE的一種模式用于在缺少顯示屏泉沾、鍵盤(pán)或者鼠標(biāo)時(shí)的系統(tǒng)配置,很
//多監(jiān)控工具如jconsole 需要將該值設(shè)置為true经瓷,系統(tǒng)變量默認(rèn)為true
configureHeadlessProperty();

//第一步:獲取并啟動(dòng)監(jiān)聽(tīng)器 SpringApplicationRunListener只有一個(gè)實(shí)現(xiàn)類(lèi)EventPublishingRunListener爆哑,
//EventPublishingRunListener有一個(gè)SimpleApplicationEventMulticaster
//SimpleApplicationEventMulticaster有一個(gè)defaultRetriver
//defaultRetriver有個(gè)屬性為applicationListeners
//每一次listeners.XXX()方法調(diào)用,都將會(huì)廣播對(duì)應(yīng)事件給applicationListeners監(jiān)聽(tīng)器處理
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();//run方法第一次被調(diào)用時(shí)舆吮,調(diào)用listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//第二步:構(gòu)造容器環(huán)境
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//設(shè)置需要忽略的bean
configureIgnoreBeanInfo(environment);
//打印banner
Banner printedBanner = printBanner(environment);
//第三步:創(chuàng)建容器
context = createApplicationContext();
//第四步:實(shí)例化SpringBootExceptionReporter.class,用來(lái)支持報(bào)告關(guān)于啟動(dòng)的錯(cuò)誤
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//第五步:準(zhǔn)備容器
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//第六步:刷新容器
refreshContext(context);
//第七步:刷新容器后的擴(kuò)展接口
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//容器已經(jīng)被刷新队贱,但是CommandLineRunners和ApplicationRunners還沒(méi)有被調(diào)用
listeners.started(context);
//調(diào)用CommandLineRunner和ApplicationRunner的run方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
//在run結(jié)束前色冀,且調(diào)用CommandLineRunner和ApplicationRunner的run方法后,調(diào)用
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

【5】SpringApplication.run方法詳細(xì)分析-獲取并啟動(dòng)監(jiān)聽(tīng)器

① 獲取監(jiān)聽(tīng)器getRunListeners

SpringApplicationRunListeners listeners = getRunListeners(args);

跟進(jìn)
SpringApplication.getRunListeners方法(返回SpringApplicationRunListeners):

private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}

上面可以看到柱嫌,args本身默認(rèn)為空锋恬,但是在獲取監(jiān)聽(tīng)器的方法中,
getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)將當(dāng)前對(duì)象作為參數(shù)编丘,該方法用來(lái)獲取spring.factories對(duì)應(yīng)的監(jiān)聽(tīng)器:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
//獲取類(lèi)加載器 WebappClassLoader
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
//根據(jù)類(lèi)加載器与学,獲取SpringApplicationRunListener(type)相關(guān)的監(jiān)聽(tīng)器
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//創(chuàng)建factories
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

Set<String> names如下:

2425d0bb1a14ea46911a2e6aeb9d988e.jpeg

整個(gè) springBoot 框架中獲取factories的方式統(tǒng)一如下:

@SuppressWarnings("unchecked")
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<>(names.size());
for (String name : names) {
try {
// //裝載class文件到內(nèi)存
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
Constructor<?> constructor = instanceClass
.getDeclaredConstructor(parameterTypes);
//主要通過(guò)反射創(chuàng)建實(shí)例
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
}
catch (Throwable ex) {
throw new IllegalArgumentException(
"Cannot instantiate " + type + " : " + name, ex);
}
}
return instances;
}

上面通過(guò)反射獲取實(shí)例時(shí)會(huì)觸發(fā)
EventPublishingRunListener的構(gòu)造函數(shù)。如下圖所示將會(huì)把a(bǔ)pplication的listener添加到SimpleApplicationEventMulticaster initialMulticaster的ListenerRetriever defaultRetriever的Set<ApplicationListener<?>> applicationListeners中:

e6737ab2187e161a0d7d6c5253a28703.jpeg

重點(diǎn)來(lái)看一下addApplicationListener方法:

public void addApplicationListener(ApplicationListener<?> listener) {
synchronized (this.retrievalMutex) {
// Explicitly remove target for a proxy, if registered already,
// in order to avoid double invocations of the same listener.
Object singletonTarget = AopProxyUtils.getSingletonTarget(listener);
if (singletonTarget instanceof ApplicationListener) {
this.defaultRetriever.applicationListeners.remove(singletonTarget);
}
this.defaultRetriever.applicationListeners.add(listener);
this.retrieverCache.clear();
}
}

上述方法定義在
SimpleApplicationEventMulticaster父類(lèi)AbstractApplicationEventMulticaster中嘉抓。關(guān)鍵代碼為this.defaultRetriever.applicationListeners.add(listener);索守,這是一個(gè)內(nèi)部類(lèi),用來(lái)保存所有的監(jiān)聽(tīng)器抑片。也就是在這一步卵佛,將spring.factories中的監(jiān)聽(tīng)器傳遞到SimpleApplicationEventMulticaster中。

繼承關(guān)系如下:

1ce47504712f0a0cd948322181de5eee.jpeg

② listeners.starting()–
SpringApplicationRunListener啟動(dòng)–監(jiān)聽(tīng)器第一次處理事件

aeeac4c7659c80c6c07629b2a9538f3e[0].jpeg

listeners.starting();,獲取的監(jiān)聽(tīng)器為
EventPublishingRunListener,從名字可以看出是啟動(dòng)事件發(fā)布監(jiān)聽(tīng)器截汪,主要用來(lái)發(fā)布啟動(dòng)事件疾牲。

SpringApplicationRunListener是run()方法的監(jiān)聽(tīng)器,其只有一個(gè)實(shí)現(xiàn)類(lèi)EventPublishingRunListener衙解。SpringApplicationRunListeners是SpringApplicationRunListener的集合類(lèi)阳柔。

也就是說(shuō)將會(huì)調(diào)用
EventPublishingRunListener的starting()方法。

public void starting() {
//關(guān)鍵代碼蚓峦,這里是創(chuàng)建application啟動(dòng)事件`ApplicationStartingEvent`
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}

EventPublishingRunListener這個(gè)是springBoot框架中最早執(zhí)行的監(jiān)聽(tīng)器舌剂,在該監(jiān)聽(tīng)器執(zhí)行started()方法時(shí),會(huì)繼續(xù)發(fā)布事件枫匾,也就是事件傳遞架诞。這種實(shí)現(xiàn)主要還是基于spring的事件機(jī)制。

繼續(xù)跟進(jìn)
SimpleApplicationEventMulticaster干茉,有個(gè)核心方法:

@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// //獲取線程池谴忧,如果為空則同步處理。這里線程池為空角虫,還未沒(méi)初始化沾谓。
Executor executor = getTaskExecutor();
if (executor != null) {
異步發(fā)送事件
executor.execute(() -> invokeListener(listener, event));
}
else {
// //同步發(fā)送事件
invokeListener(listener, event);
}
}
}

ApplicationListener<E extends ApplicationEvent>接口有個(gè)抽象方法onApplicationEvent(E event)子類(lèi)必須實(shí)現(xiàn)。該方法用來(lái)處理對(duì)應(yīng)事件戳鹅。

其中g(shù)etApplicationListeners(event, type)主要有四種listener:

  • LoggingApplicationListener(處理日志)
  • BackgroundPreinitializer
  • DelegatingApplicationListener
  • LiquibaseServiceLocatorApplicationListener

這是springBoot啟動(dòng)過(guò)程中均驶,第一處根據(jù)類(lèi)型,執(zhí)行監(jiān)聽(tīng)器的地方枫虏。根據(jù)發(fā)布的事件類(lèi)型從上述10種監(jiān)聽(tīng)器中選擇對(duì)應(yīng)的監(jiān)聽(tīng)器進(jìn)行事件發(fā)布妇穴,當(dāng)然如果繼承了 springCloud或者別的框架,就不止10個(gè)了隶债。這里選了一個(gè) springBoot 的日志監(jiān)聽(tīng)器來(lái)進(jìn)行講解腾它,核心代碼如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
//在springboot啟動(dòng)的時(shí)候
if (event instanceof ApplicationStartedEvent) {
onApplicationStartedEvent((ApplicationStartedEvent) event);
}
//springboot的Environment環(huán)境準(zhǔn)備完成的時(shí)候
else if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent(
(ApplicationEnvironmentPreparedEvent) event);
}
//在springboot容器的環(huán)境設(shè)置完成以后
else if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
//容器關(guān)閉的時(shí)候
else if (event instanceof ContextClosedEvent && ((ContextClosedEvent) event)
.getApplicationContext().getParent() == null) {
onContextClosedEvent();
}
//容器啟動(dòng)失敗的時(shí)候
else if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}

因?yàn)槲覀兊氖录?lèi)型為ApplicationEvent,所以會(huì)執(zhí)行onApplicationStartedEvent((ApplicationStartedEvent) event);死讹。springBoot會(huì)在運(yùn)行過(guò)程中的不同階段瞒滴,發(fā)送各種事件,來(lái)執(zhí)行對(duì)應(yīng)監(jiān)聽(tīng)器的對(duì)應(yīng)方法赞警。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妓忍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子愧旦,更是在濱河造成了極大的恐慌世剖,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忘瓦,死亡現(xiàn)場(chǎng)離奇詭異搁廓,居然都是意外死亡引颈,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)境蜕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蝙场,“玉大人,你說(shuō)我怎么就攤上這事粱年∈勐耍” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵台诗,是天一觀的道長(zhǎng)完箩。 經(jīng)常有香客問(wèn)我,道長(zhǎng)拉队,這世上最難降的妖魔是什么弊知? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮粱快,結(jié)果婚禮上秩彤,老公的妹妹穿的比我還像新娘。我一直安慰自己事哭,他們只是感情好漫雷,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著鳍咱,像睡著了一般降盹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谤辜,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天蓄坏,我揣著相機(jī)與錄音,去河邊找鬼丑念。 笑死剑辫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的渠欺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼椎眯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼挠将!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起编整,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤舔稀,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后掌测,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體内贮,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了夜郁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片什燕。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖竞端,靈堂內(nèi)的尸體忽然破棺而出屎即,到底是詐尸還是另有隱情,我是刑警寧澤事富,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布技俐,位于F島的核電站,受9級(jí)特大地震影響统台,放射性物質(zhì)發(fā)生泄漏雕擂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一贱勃、第九天 我趴在偏房一處隱蔽的房頂上張望井赌。 院中可真熱鬧,春花似錦募寨、人聲如沸族展。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)仪缸。三九已至,卻和暖如春列肢,著一層夾襖步出監(jiān)牢的瞬間恰画,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工瓷马, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拴还,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓欧聘,卻偏偏與公主長(zhǎng)得像片林,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怀骤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容