概述我們知道 Spring Boot 應用能夠被打成 war 包,放入外部 Tomcat 容器中運行。你是否知道 Spring Boot 是如何整合 Spring MVC 的呢?如何使用在我們的 Spring Boot 項目中通
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 自動配置註解的實現原理
轉發分享此文,後臺私信小編:「 資料 」即可獲取。(注:轉發分享,感謝大家)
相關文章
概述我們知道 Spring Boot 應用能夠被打成 war 包,放入外部 Tomcat 容器中運行。你是否知道 Spring Boot 是如何整合 Spring MVC 的呢?如何使用在我們的 Spring Boot 項目中通
2021-07-05 03:09:37
在華為手機因為晶片問題銷量不斷下滑的背景下,小米成功接過華為手中接力棒,成為全球手機市場上的中國品牌代表。IDC資料顯示,小米已經連續兩個季度穩坐全球第三寶座,銷量遙遙領
2021-07-05 03:04:17
大資料文摘授權轉載自AI科技評論 作者:陳大鑫 7月1日晚,國際大學生超算競賽(ISC2021)總決賽結果公佈,清華大學學生超算團隊奪得總冠軍,暨南大學代表隊首次受邀參賽並奪得季軍。另
2021-07-05 03:03:55
明敏 發自 凹非寺 量子位 報道 | 公眾號 QbitAI如果在禿頭和肥胖中,一定要你做個選擇,你會哪一個?寧胖不禿!反正也瘦不下來而且這年頭,一頭烏黑靚麗的秀髮,也是一筆可以炫耀的資本
2021-07-05 03:03:40
iPhone12 Pro 銷量更高近日各大研究統計資料表示,iPhone12 系列中銷量最高的居然不是 iPhone12 基礎版,而是更貴的 iPhone12 Pro 裝置。按照之前銷量來看,這代的 iPhone12 銷量
2021-07-05 03:03:18
原本很多網友覺得會是小米拿下屏下攝像頭的首發權,因為小米在去年已經開始公佈了屏下攝像頭技術,聲稱首發屏下攝像頭技術,再加上小米經常喜歡搶首發,比如驍龍888,就是小米首發,還
2021-07-05 03:03:11