首頁 > 科技

Spring Boot源碼分析——支援外部 Tomcat 容器的實現詳解

2021-07-05 03:09:37

概述

我們知道 Spring Boot 應用能夠被打成 war 包,放入外部 Tomcat 容器中運行。你是否知道 Spring Boot 是如何整合 Spring MVC 的呢?

如何使用

在我們的 Spring Boot 項目中通常會引入 spring-boot-starter-web 這個依賴,該模組提供全棧的 WEB 開發特性,包括 Spring MVC 依賴和 Tomcat 容器,我們將內部 Tomcat 的 Starter 模組排除掉,如下:

然後啟動類這樣寫:

這樣你打成 war 包就可以放入外部的 Servlet 容器中運行了。

實現原理

原理在分析 Spring MVC 源碼的時候講過,參考我的 《精盡Spring MVC源碼分析 - 尋找遺失的 web.xml》這篇文章

藉助於 Servlet 3.0 的一個新特性,新增的一個 javax.servlet.ServletContainerInitializer 介面,在 Servlet 容器啟動時會通過 Java 的 SPI 機制從 META-INF/services/javax.servlet.ServletContainerInitializer 檔案中找到這個介面的實現類,然後呼叫它的 onStartup(..) 方法。

在 Spring 的 spring-web 模組中該檔案是這麼配置的:

一起來看看這個類:

通過 @HandlesTypes 註解指定只處理 WebApplicationInitializer 類型的類

這個過程很簡單,例項化所有 WebApplicationInitializer 類型的物件,然後依次呼叫它們的 onStartup(ServletContext) 方法

通過打斷點你會發現,有一個 DemoApplication 就是我們的啟動類

這也就是為什麼如果你的 Spring Boot 應用需要打成 war 包放入外部 Tomcat 容器運行的時候,你的啟動類需要繼承 SpringBootServletInitializer 這個抽象類,因為這個抽象類實現類 WebApplicationInitializer 介面,我們只需要繼承它即可

SpringBootServletInitializer

org.springframework.boot.web.servlet.support.SpringBootServletInitializer 抽象類,實現了 WebApplicationInitializer 介面,目的就是支援你將 Spring Boot 應用打包成 war 包放入外部的 Servlet 容器中運行

在 onStartup(ServletContext) 方法中就兩步:

呼叫 createRootApplicationContext(ServletContext) 方法,創建一個 WebApplicationContext 作為 Root Spring 應用上下文新增一個 ContextLoaderListener 監聽器,會監聽到 ServletContext 的啟動事件,因為 Spring 應用上下文在上面第 1 步已經準備好了,所以這裡什麼都不用做第 1 步是不是和 Spring MVC 類似,同樣創建一個 Root WebApplicationContext 作為 Spring 應用上下文的父物件

createRootApplicationContext 方法

createRootApplicationContext(ServletContext) 方法,創建一個 Root WebApplicationContext 物件,如下:

過程如下:

創建一個 SpringApplication 構造器,目的就是啟動 Spring 應用咯

設定 mainApplicationClass,也就是你的啟動類,主要用於列印日誌從 ServletContext 上下文中獲取最頂部的 Root ApplicationContext 應用上下文 parent,通常這裡沒有父物件,所以為空如果 parent 不為空,則先 ServletContext 中的該屬性置空,因為這裡會創建一個 ApplicationContext 作為 Root新增一個 ApplicationContextInitializer 初始器,用於設定現在要創建的 Root ApplicationContext 應用上下文的父容器為 parent新增一個 ApplicationContextInitializer 初始器,目的是往 ServletContext 上下文中設定 Root ApplicationContext 為現在要創建的 Root ApplicationContext 應用上下文,並將這個 ServletContext 儲存至 ApplicationContext 中注意,這個物件很關鍵,會將當前 ServletContext 上下文物件設定到 ApplicationContext 物件裡面,那麼後續就不會再創建 Spring Boot 內嵌的 Tomcat 了設定要創建的 Root ApplicationContext 應用上下文的類型(Servlet)對 SpringApplicationBuilder 進行擴展,呼叫 configure(SpringApplicationBuilder) 方法,這也就是為什麼我們的啟動類可以重寫該方法,通常不用做什麼新增一個 ApplicationListener 監聽器,用於將 ServletContext 中的相關屬性關聯到 Environment 環境中構建一個 SpringApplication 物件 application,用於啟動 Spring 應用如果沒有設定 source 源物件,那麼這裡嘗試設定為當前 Class 物件,需要有 @Configuration 註解因為 SpringApplication 在創建 ApplicationContext 應用上下文的過程中需要優先註冊 source 源物件,如果為空則拋出異常新增一個錯誤頁面 Filter 作為 sources呼叫 application 的 run 方法啟動整個 Spring Boot 應用整個過程不復雜,SpringApplication 相關的內容在前面的 《SpringApplication 啟動類的啟動過程》文章中已經分析過,這裡的關鍵在於第 5 步

新增的 ServletContextApplicationContextInitializer 會將當前 ServletContext 上下文物件設定到 ApplicationContext 物件裡面

ServletContextApplicationContextInitializer

可以看到會將這個 ServletContext 上下文物件設定到 ApplicationContext 中

那麼我們回顧到上一篇 《Spring Boot 內嵌 Tomcat 容器的實現》 文章的 1. onRefresh 方法小節呼叫的 createWebServer() 方法,如下:

我們看到上面第 4 步,如果從當前 Spring 應用上下文獲取到了 ServletContext 物件,不會走上面的第 3 步,也就是不創建 Spring Boot 內嵌的 Tomcat

主動呼叫它的 getSelfInitializer() 方法來往這個 ServletContext 物件中註冊各種 Servlet、Filter 和 EventListener 物件,包括 Spring MVC 中的 DispatcherServlet物件,該方法參考上一篇 《Spring Boot 內嵌 Tomcat 容器的實現》 文章的 2. selfInitialize 方法 小節

總結

本文分析了 Spring Boot 應用被打成 war 包後是如何支援放入外部 Tomcat 容器運行的,原理也比較簡單,藉助 Spring MVC 中的 SpringServletContainerInitializer 這個類,它實現了 Servlet 3.0 新增的 javax.servlet.ServletContainerInitializer 介面

通過 Java 的 SPI 機制,在 META-INF/services/javax.servlet.ServletContainerInitializer 檔案中寫入 SpringServletContainerInitializer 這個類,那麼在 Servlet 容器啟動的時候會呼叫這個類的 onStartup(..) 方法,會找到 WebApplicationInitializer 類型的物件,並呼叫他們的 onStartup(ServletContext) 方法在我們的 Spring Boot 應用中,如果需要打成 war 包放入外部 Tomcat 容器運行,啟動類則需要繼承 SpringBootServletInitializer 抽象類,它實現了 WebApplicationInitializer 介面在 SpringBootServletInitializer 中會創建一個 WebApplicationContext 作為 Root Spring 應用上下文,同時會將 ServletContext 物件設定到 Spring 應用上下文中這樣一來,因為已經存在 ServletContext 物件,那麼不會再創建 Spring Boot 內嵌的 Tomcat 容器,而是對 ServletContext 進行一些初始化工作好了,到這裡關於 Spring Boot 啟動 Spring 應用的整個主流程,包括內嵌 Tomcat 容器的實現,以及支援運行在外部 Servlet 容器的實現都分析完了

那麼接下來,我們一起來看看 @SpringBootApplication 這個註解,也就是 @EnableAutoConfiguration 自動配置註解的實現原理

轉發分享此文,後臺私信小編:「 資料 」即可獲取。(注:轉發分享,感謝大家)


IT145.com E-mail:sddin#qq.com