我在初學spring的時候,很懵逼,因為整個專案中不存在main方法,讓我有點摸不著頭腦。那時候我知道有個東西叫tomcat是它監聽了埠,解析了協定調到了我的servlet。
在我初學SpringBoot的時候,很懵逼,有main方法了,但是tomcat在哪裡呢,又是如何啟動起來的?。
在Tomcat總體架構,啟動流程與處理請求流程中學習了tomcat總體架構和啟動流程。
在springboot內嵌tomcat中則不再使用BootStrap->Catalina這種方式進行啟動,而是跨過這一層直接啟動了Server。
下圖是SpringBoot的啟動流程,其中紅色框框是SpringBoot推斷ApplicationContext的步驟
這是我們學習springboot內嵌tomcat啟動的切入點。
在springboot啟動的時候會根據classpath推斷當前web應用型別。如果在classpath中存在org.springframework.web.reactive.DispatcherHandler那麼會視為REACTIVE型別,如果存在javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext
那麼會視為SERVLET型別
SpringBoot會使用ApplicationContextFactory去構建出一個ApplicationContext,一般預設使用DefaultApplicationContextFactory,DefaultApplicationContextFactory會從spring.factories中找出所有的ApplicationContextFactory實現類根據webApplication型別去建立
其中有SERVLET型別對應的AnnotationConfigServletWebServerApplicationContext內部類Factory
和REACTIVE型別對應的AnnotationConfigReactiveWebServerApplicationContext內部類Factory。
在不使用spring 響應式程式設計的情況下這裡都會使用AnnotationConfigServletWebServerApplicationContext內部類Factory
去構建
也就是說SpringBoot的啟動會使用SpringApplication構建出AnnotationConfigServletWebServerApplicationContext最為Spring上下文。
在SpringBoot啟動的過程中,SpringApplication會觸發Spring上下文(ApplicationContext,也就是AnnotationConfigServletWebServerApplicationContext)的重新整理
AnnotationConfigServletWebServerApplicationContext
是AbstractApplicationContext的子類,其refresh重新整理方法由AbstractApplicationContext進行了實現。大致流程如下
其中onRefresh勾點方法便會觸發Tomcat的啟動,onRefrsh由 AnnotationConfigServletWebServerApplicationContext
父類別ServletWebServerApplicationContext
進行了實現
下圖是SpringBoot啟動web伺服器的全流程
WebServerFactory
是一個標記介面,ServletWebServerFactory
中定義了方法getWebServer(ServletContextInitializer... initializers)
來建立WebServer,如參ServletContextInitializer
是函數式介面,具備方法onStartup來進行回撥。
預設設定下SpringBoot將使用TomcatServletWebServerFactory
來建立WebServer。
WebServer是springboot對伺服器的抽象,具備start,stop,getPort,shutDownGracefully(優雅停)方法。
組裝生成TomcatServer主要依賴於Tomcat這個類,Tomcat是嵌入式tomcat啟動器,提供眾多api來組裝tomcat伺服器。這一步其實就是在組裝tomcat容器模型。
可以看到Tomcat中具備Server屬性,這代表了web應用伺服器
在Server中具備Service陣列屬性表示web應用伺服器中眾多的服務
這裡只有一個服務——StandardService,其中包含Connector陣列和Engine陣列
Connector
Connector負責監聽使用者端請求,返回響應資料。
Connector內部由ProtocalHandler屬性,其使用AbstractEndpoint監聽埠,並將請求交給Processor處理
Engine
Engine負責處理具體的請求。
Engine是一個Container內部使用HashMap維護Host(key是hostName,value是Host物件)
Host也是一個Container,內部同一使用HashMap維護Context(key是上下文名稱,value是Context物件)
最終將設定Tomcat物件到TomcatWebServer屬性上,並呼叫Tomcat#start啟動Tomcat伺服器
TomcatWebServer會呼叫Tomcat.start方法來啟動Tomcat,整個啟動流程和原生Tomcat一致
SpringBoot中如果使用web-stater,那麼會引入DispatcherServlet
的自動裝配,這也就是為什麼Tomcat接收到的請求會來到DispatcherServlet
,然後由DispatcherServlet
反射呼叫到Controller的方法。
那麼DispatcherServlet
是什麼時候被加入到Tomcat中的呢?
在TomcatReactiveWebServerFactory#configureContext
方法中會註冊TomcatStarter
到TomcatEmbeddedContext
中
TomcatEmbeddedContext
是StandardContext
的子類,在TomcatEmbeddedContext
被呼叫start
的時候,會拿出所有的ServletContainerInitializer
呼叫其onStartup
這裡便會拿到TomcatStarter
,TomcatStarter
使用ServletContextInitializer
陣列記錄了ServletWebServerApplicationContext#selfInitialize
方法,從而實現ServletWebServerApplicationContext#selfInitialize
的回撥
SpringBoot之所以這麼做是因為ServletContainerInitializer
是Servlet規範介面,而ServletContextInitializer
是SpringBoot
定義的介面,利用TomcatStarter將SpringBoot定義的介面嫁接到Servlet定義的規範中
,從而保證當用戶將SpringBoot打包成war包也能觸發ServletWebServerApplicationContext#selfInitialize
那麼ServletWebServerApplicationContext#selfInitialize
做了什麼
private void selfInitialize(ServletContext servletContext) throws ServletException {
// <1> 將當前 Spring 應用上下文設定到 ServletContext 上下文的屬性中
// 同時將 ServletContext 上下文設定到 Spring 應用上下文中
prepareWebApplicationContext(servletContext);
// <2> 向 Spring 應用上下文註冊一個 ServletContextScope 物件(ServletContext 的封裝)(這就是application這種bean作用域生效的本原因)
registerApplicationScope(servletContext);
// <3> 向 Spring 應用上下文註冊 `contextParameters` 和 `contextAttributes` 屬性
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
/**
* <4> 【重點】先從 Spring 應用上下文找到所有的 ServletContextInitializer
* 也就會找到各種 RegistrationBean,然後依次呼叫他們的 `onStartup` 方法,向 ServletContext 上下文註冊 Servlet、Filter 和 EventListener
* 例如 DispatcherServletAutoConfiguration DispatcherServletRegistrationBean 就會註冊 @link DispatcherServlet 物件
* 所以這裡執行完了,也就啟動了 Tomcat,同時註冊了所有的 Servlet,那麼 Web 應用準備就緒了
*/
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
beans.onStartup(servletContext);
}
}
Spring會從BeanFactory拿到所有ServletContextInitializer的實現
註冊其實都是回撥這些ServletContextInitializer的onStartup方法,DispatcherServletRegistrationBean則會將DispatcherServlet註冊到ServletContext中。
TomcatEmbeddedContext
會將DispatcherServlet保證成Wrapper加入到TomcatEmbeddedContext中去。
這一波學習,讓我深刻的理解了Tomcat容器模型,也瞭解到SpringBoot中使用Filter或者Servlet的時候,為什麼要向Spring注入對應的RegsitrationBean,因為只有這樣ServletWebServerApplicationContext才能從容器中獲取到RegsitrationBean並註冊到Tomcat中。