1. 程式人生 > 實用技巧 >SpringBoot中的Tomcat是如何啟動的?

SpringBoot中的Tomcat是如何啟動的?

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

新增如上 Web 的依賴,Spring Boot 就幫我們內建了 Servlet 容器,預設使用的是 Tomcat,同樣也支援修改,比如可以使用 jetty、Undertow 等。

因為內建了啟動容器,應用程式可以直接通過 Maven 命令將專案編譯成可執行的 jar 包,通過 java -jar 命令直接啟動,不需要再像以前一樣,打包成 War 包,然後部署在 Tomcat 中。

那麼:你知道內建的 Tomcat 在 Spring Boot 中是怎麼啟動的嗎?

從啟動入口分析

如果不知道從哪開始,那麼至少應該知道 Spring Boot 其實執行的就是一個 main 方法,

本文環境:Spring Boot:2.2.2.RELEASE

@SpringBootApplication
publicclassMyApplication{
publicstaticvoidmain(String[]args){
SpringApplication.run(MyApplication.class,args);
}
}

我們點進這個 SpringApplication.run() 方法:

publicstaticConfigurableApplicationContextrun(Class<?>primarySource,String...args){
returnrun(newClass[]{primarySource},args);
}

這列的 run() 方法返回的是 ConfigurableApplicationContext 物件,我們繼續跟蹤這個 run() 方法:

publicstaticConfigurableApplicationContextrun(Class<?>[]primarySources,String[]args){
return
(newSpringApplication(primarySources)).run(args);
}

又套了一層,繼續點選這個返回的 run() 方法:

publicConfigurableApplicationContextrun(String...args){
StopWatchstopWatch=newStopWatch();
stopWatch.start();
ConfigurableApplicationContextcontext=null;
Collection<SpringBootExceptionReporter>exceptionReporters=newArrayList<>();
/*
*1、配置屬性
*設定系統屬性java.awt.headless,為true則啟用headless模式
*headless模式是應用的一種配置模式,在伺服器缺少顯示裝置、鍵盤、滑鼠等外設的情況下可以使用該模式
*比如我們使用的Linux伺服器就是缺少前述的這些裝置,但是又需要使用這些裝置提供的能力
*/
configureHeadlessProperty();
/*
*2、獲取監聽器,釋出應用開始啟動事件
*通過SpringFactoriesLoader檢索META-INF/spring.factories,
*找到宣告的所有SpringApplicationRunListener的實現類並將其例項化,
*之後逐個呼叫其started()方法,廣播SpringBoot要開始執行了
*/
SpringApplicationRunListenerslisteners=getRunListeners(args);
/*釋出應用開始啟動事件*/
listeners.starting();
try{
/*3、初始化引數*/
ApplicationArgumentsapplicationArguments=newDefaultApplicationArguments(
args);
/*
*4、配置環境,輸出banner
*建立並配置當前SpringBoot應用將要使用的Environment(包括配置要使用的PropertySource以及Profile),
*並遍歷呼叫所有的SpringApplicationRunListener的environmentPrepared()方法,廣播Environment準備完畢。
*/
ConfigurableEnvironmentenvironment=prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
/*列印banner,如果在resources目錄下建立了我們自己的banner就會進行列印,否則預設使用spring的*/
BannerprintedBanner=printBanner(environment);
/*5、建立應用上下文*/
context=createApplicationContext();
/*通過SpringFactoriesLoader檢索META-INF/spring.factories,獲取並例項化異常分析器。*/
exceptionReporters=getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
newClass[]{ConfigurableApplicationContext.class},context);
/*
*6、預處理上下文
*為ApplicationContext載入environment,之後逐個執行ApplicationContextInitializer的initialize()方法來進一步封裝ApplicationContext,
*並呼叫所有的SpringApplicationRunListener的contextPrepared()方法,【EventPublishingRunListener只提供了一個空的contextPrepared()方法】,
*之後初始化IoC容器,並呼叫SpringApplicationRunListener的contextLoaded()方法,廣播ApplicationContext的IoC載入完成,
*這裡就包括通過@EnableAutoConfiguration匯入的各種自動配置類。
*/
prepareContext(context,environment,listeners,applicationArguments,
printedBanner);
/*7、重新整理上下文*/
refreshContext(context);
/*8、再一次重新整理上下文,其實是空方法,可能是為了後續擴充套件。*/
afterRefresh(context,applicationArguments);
stopWatch.stop();
if(this.logStartupInfo){
newStartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(),stopWatch);
}
/*9、釋出應用已經啟動的事件*/
listeners.started(context);
/*
*遍歷所有註冊的ApplicationRunner和CommandLineRunner,並執行其run()方法。
*我們可以實現自己的ApplicationRunner或者CommandLineRunner,來對SpringBoot的啟動過程進行擴充套件。
*/
callRunners(context,applicationArguments);
}
catch(Throwableex){
handleRunFailure(context,ex,exceptionReporters,listeners);
thrownewIllegalStateException(ex);
}

try{
/*10、釋出應用已經啟動完成的監聽事件*/
listeners.running(context);
}
catch(Throwableex){
handleRunFailure(context,ex,exceptionReporters,null);
thrownewIllegalStateException(ex);
}
returncontext;
}

如果覺得這個方法看的雲裡霧裡的,那麼可以概括為如下幾步:

  1. 配置系統屬性
  2. 獲取監聽,釋出應用開始啟動時間
  3. 初始化輸入引數
  4. 配置環境,輸出banner
  5. 建立上下文
  6. 預處理上下文
  7. 重新整理上下文
  8. 再次重新整理上下文
  9. 釋出應用已經啟動事件
  10. 釋出應用啟動完成事件

而我們 Tomcat 的啟動主要是在第5步建立上下文,以及第 7步重新整理上下文實現的。

建立上下文

第5步中,建立上下文主要是呼叫的 createApplicationContext() 方法:

protectedConfigurableApplicationContextcreateApplicationContext(){
/**1.根據Web應用型別,獲取對應的ApplicationContext子類**/
Class<?>contextClass=this.applicationContextClass;
if(contextClass==null){
try{
switch(this.webApplicationType){
caseSERVLET:
contextClass=Class.forName("org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext");
break;
caseREACTIVE:
contextClass=Class.forName("org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass=Class.forName("org.springframework.context.annotation.AnnotationConfigApplicationContext");
}
}catch(ClassNotFoundExceptionvar3){
thrownewIllegalStateException("UnablecreateadefaultApplicationContext,pleasespecifyanApplicationContextClass",var3);
}
}
/**2.例項化子類**/
return(ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}

程式碼中主要就是通過 switch 語句,根據 webApplicationType 的型別來建立不同的 ApplicationContext:

  • SERVLET:Web型別,例項化 AnnotationConfigServletWebServerApplicationContext
  • REACTIVE:響應式Web型別,例項化 AnnotationConfigReactiveWebServerApplicationContext
  • default:非Web型別,例項化 AnnotationConfigApplicationContext

因為我們的應用是 Web 型別,所以例項化的是 AnnotationConfigServletWebServerApplicationContext,如下是該類的關係圖(由Diagram截圖):

我們在上圖的底部觸發,可以看到 AnnotationConfigServletWebServerApplicationContext > ServletWebServerApplicationContext > ... AbstractApplicationContext(>表示繼承),總之,最終繼承到了 AbstractApplicationContext,這個類是 ApplicationContext 的抽象實現類,該抽象類實現應用上下文的一些具體操作。

至此,並沒有看到 Tomcat 的相關程式碼,其實這一步主要就是「建立上下文」,拿到「上下文」之後需要傳遞給「重新整理上下文」,交由重新整理上下文建立 Web 服務。

重新整理上下文

第7步中,重新整理上下文時呼叫的 refreshContext(context) 方法,其中 context 就是第5步建立的上下文,方法如下:

privatevoidrefreshContext(ConfigurableApplicationContextcontext){
refresh(context);
if(this.registerShutdownHook){
try{
context.registerShutdownHook();
}
catch(AccessControlExceptionex){
//Notallowedinsomeenvironments.
}
}
}

protectedvoidrefresh(ApplicationContextapplicationContext){
Assert.isInstanceOf(AbstractApplicationContext.class,applicationContext);
((AbstractApplicationContext)applicationContext).refresh();
}

refreshContext() 方法傳遞的 context,經由 refresh() 方法強轉成父類 AbstractApplicationContext,具體呼叫過程如下:

publicvoidrefresh()throwsBeansException,IllegalStateException{
synchronized(this.startupShutdownMonitor){
this.prepareRefresh();
ConfigurableListableBeanFactorybeanFactory=this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);

try{
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
/**主要關係onRefresh()方法-------------**/
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
}catch(BeansExceptionvar9){
if(this.logger.isWarnEnabled()){
this.logger.warn("Exceptionencounteredduringcontextinitialization-cancellingrefreshattempt:"+var9);
}

this.destroyBeans();
this.cancelRefresh(var9);
throwvar9;
}finally{
this.resetCommonCaches();
}

}
}

在這個方法中我們主要關心 onRefresh() 方法,onRefresh() 方法是呼叫其子類實現的,也就是 ServletWebServerApplicationContext,

如下是子類的 onRefresh() 方法:

protectedvoidonRefresh(){
super.onRefresh();

try{
this.createWebServer();
}catch(Throwablevar2){
thrownewApplicationContextException("Unabletostartwebserver",var2);
}
}

privatevoidcreateWebServer(){
WebServerwebServer=this.webServer;
ServletContextservletContext=this.getServletContext();
if(webServer==null&&servletContext==null){
/**得到Servlet工廠**/
ServletWebServerFactoryfactory=this.getWebServerFactory();
this.webServer=factory.getWebServer(newServletContextInitializer[]{this.getSelfInitializer()});
}elseif(servletContext!=null){
try{
this.getSelfInitializer().onStartup(servletContext);
}catch(ServletExceptionvar4){
thrownewApplicationContextException("Cannotinitializeservletcontext",var4);
}
}
this.initPropertySources();
}

其中 createWebServer() 方法是用來啟動web服務的,但是還沒有真正啟動 Tomcat,只是通過ServletWebServerFactory 建立了一個 WebServer,我們繼續來看這個 ServletWebServerFactory:

ServletWebServerFactory 有4個實現類,其中我們最常用的是 TomcatServletWebServerFactory和JettyServletWebServerFactory,而預設的 Web 環境就是 TomcatServletWebServerFactory。

而到這總算是看到 Tomcat 相關的字眼了。

來看一下 TomcatServletWebServerFactory 的 getWebServer() 方法:

publicWebServergetWebServer(ServletContextInitializer...initializers){
if(this.disableMBeanRegistry){
Registry.disableRegistry();
}
/**1、建立Tomcat例項**/
Tomcattomcat=newTomcat();
FilebaseDir=this.baseDirectory!=null?this.baseDirectory:this.createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connectorconnector=newConnector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
this.customizeConnector(connector);
/**2、給建立好的tomcat設定聯結器connector**/
tomcat.setConnector(connector);
/**設定不自動部署**/
tomcat.getHost().setAutoDeploy(false);
/**3、配置Tomcat容器引擎**/
this.configureEngine(tomcat.getEngine());
Iteratorvar5=this.additionalTomcatConnectors.iterator();

while(var5.hasNext()){
ConnectoradditionalConnector=(Connector)var5.next();
tomcat.getService().addConnector(additionalConnector);
}
/**
*準備Tomcat的StandardContext,並新增到Tomcat中,同時把initializers註冊到型別為
*TomcatStarter的ServletContainerInitializer中
**/
this.prepareContext(tomcat.getHost(),initializers);
/**將建立好的Tomcat包裝成WebServer返回**/
returnthis.getTomcatWebServer(tomcat);
}

publicEnginegetEngine(){
Serviceservice=this.getServer().findServices()[0];
if(service.getContainer()!=null){
returnservice.getContainer();
}else{
Engineengine=newStandardEngine();
engine.setName("Tomcat");
engine.setDefaultHost(this.hostname);
engine.setRealm(this.createDefaultRealm());
service.setContainer(engine);
returnengine;
}
}

getWebServer() 這個方法建立了 Tomcat 物件,並且做了兩件重要的事情:

  1. 把聯結器 Connector 物件新增到 Tomcat 中;
  2. 配置容器引擎,configureEngine(tomcat.getEngine());

首先說一下這個 Connector 聯結器,Tomcat 有兩個核心功能:

  1. 處理 Socket 連線,負責網路位元組流與 Request 和 Response 物件的轉化。
  2. 載入和管理 Servlet,以及具體處理 Request 請求。

針對這兩個功能,Tomcat 設計了兩個核心元件來分別完成這兩件事,即:聯結器(Connector)和容器(Container)。

整個過程大致就是:Connector 聯結器接收連線請求,建立Request和Response物件用於和請求端交換資料,然後分配執行緒讓Engine(也就是Servlet容器)來處理這個請求,並把產生的Request和Response物件傳給Engine。當Engine處理完請求後,也會通過Connector將響應返回給客戶端。

這裡面提到了 Engine,這個是 Tomcat 容器裡的頂級容器(Container),我們可以通過 Container 類檢視其他的子容器:Engine、Host、Context、Wrapper

4者的關係是:Engine 是最高級別的容器,Engine 子容器是 Host,Host 的子容器是 Context,Context 子容器是 Wrapper,所以這4個容器的關係就是父子關係,即:Wrapper > Context > Host > Engine (>表示繼承)

至此我們瞭解了 Engine 這個就是個容器,然後我們再看一下這個 configureEngine(tomcat.getEngine()) 具體幹了啥:

privatevoidconfigureEngine(Engineengine){
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
Iteratorvar2=this.engineValves.iterator();
while(var2.hasNext()){
Valvevalve=(Valve)var2.next();
engine.getPipeline().addValve(valve);
}
}

其中 engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay) 是指定背景執行緒的執行間隔,例如背景執行緒會在每隔多長時間後判斷session是否失效之類。

再回到 getWebServer() 方法,最終 getWebServer() 方法返回了 TomcatWebServer。

return this.getTomcatWebServer(tomcat);

通過 getTomcatWebServer() 方法,繼續下沉:

/**
*建構函式例項化TomcatWebServer
**/
publicTomcatWebServer(Tomcattomcat,booleanautoStart){
this.monitor=newObject();
this.serviceConnectors=newHashMap();
Assert.notNull(tomcat,"TomcatServermustnotbenull");
this.tomcat=tomcat;
this.autoStart=autoStart;
this.initialize();
}

privatevoidinitialize()throwsWebServerException{
/**我們在啟動SpringBoot時經常看到列印這句話**/
logger.info("Tomcatinitializedwithport(s):"+this.getPortsDescription(false));
synchronized(this.monitor){
try{
this.addInstanceIdToEngineName();
Contextcontext=this.findContext();
context.addLifecycleListener((event)->{
if(context.equals(event.getSource())&&"start".equals(event.getType())){
this.removeServiceConnectors();
}

});
/**啟動tomcat**/
this.tomcat.start();
this.rethrowDeferredStartupExceptions();

try{
ContextBindings.bindClassLoader(context,context.getNamingToken(),this.getClass().getClassLoader());
}catch(NamingExceptionvar5){
}

this.startDaemonAwaitThread();
}catch(Exceptionvar6){
this.stopSilently();
this.destroySilently();
thrownewWebServerException("UnabletostartembeddedTomcat",var6);
}

}
}

至此,Tomcat 啟動過程就很清晰了,總結一下。

總結

SpringBoot的啟動主要是通過例項化SpringApplication來啟動的,啟動過程主要做了如下幾件事情:

  1. 配置系統屬性
  2. 獲取監聽,釋出應用開始啟動時間
  3. 初始化輸入引數
  4. 配置環境,輸出banner
  5. 建立上下文
  6. 預處理上下文
  7. 重新整理上下文
  8. 再次重新整理上下文
  9. 釋出應用已經啟動事件
  10. 釋出應用啟動完成事件

而啟動 Tomcat 是在第7步 重新整理上下文 這一步。

從整個流轉過程中我們知道了 Tomcat 的啟動主要是例項化兩個元件:Connector、Container。

  • Spring Boot 建立 Tomcat 時,會先建立一個根上下文,將 WebApplicationContext 傳給 Tomcat;

  • 啟動 Web 容器,需要呼叫 getWebserver(),因為預設的 Web 環境就是 TomcatServletWebServerFactory,所以會建立 Tomcat 的 Webserver,這裡會把根上下文作為引數給 TomcatServletWebServerFactory 的 getWebServer();

  • 啟動 Tomcat,呼叫 Tomcat 中 Host、Engine 的啟動方法。

部落格園:https://www.cnblogs.com/niceyoo