如何給Spring Boot 的嵌入式 Tomcat 部署多個應用?
Spring Boot 的應用,大都有這樣的特別,你在添加了依賴之后,即使是 Web 應用,最終也可以通過 JAR 的形式運行,具體依賴的容器環境,則通過嵌入式的形式隱式的使用。
而像這些環境,Spring 的配置等,更多的隱藏在 Spring Boot 的內部,開發者可以更多的專注于「業務邏輯」的開發。
「解放了雙手」的時候,話說回來,某些時候,也是有一些弊端的。比如像之前通過 WAR 文件的形式獨立部署時,可以在容器內再額外部署一些「監控」應用,來觀察容器的情況,應用的請求情況等,這些內容在嵌入式的時候,就有些辦不從心了。
那對于 習慣了 Spring Boot 的 JAR 文件便捷運行的用戶,有沒有辦法,能在保留 JAR 使用習慣的前提下,又能部署其他應用,來滿足獨立容器部署的形式和使用習慣呢?
答案是有的。魚和熊掌,也可得兼。 后面我們會以嵌入式的 Tomcat 為例,來說明具體的實現方式。
首先,我們需要認識這一點,對于嵌入式的容器,他本質上依然還是容器,保留了容器的絕大數內容。所以,一些獨立部署時的風格,接口也依然可以使用。
不熟悉 Spring Boot 內 Tomcat 工作原理的讀者,可以參考這幾篇舊文:
我們前面說,嵌入式容器,也還是容器,所以我們只要「拿到」這個容器,就可以對其進行操作了。
舊文里我們提過, Spring Boot 內的嵌入式 Tomcat,是自己 new 了一個Tomcat 實例出來,再把應用做為 Context 部署進去。我們要想部署其他的應用,也照著「葫蘆」拿到 這個實例,部署應用。
Spring Boot 內,由于要支持各種 Servlet 容器,所以統一進行了抽象了創建容器的Factory,在 Spring Boot 1.x 和 2.x分別由
EmbeddedServletContainerFactory 和 ServletWebServerFactory 這兩個接口表示。 而對應的工廠里創建出來的容器對象,在 1.x 和 2.x 中,分別由TomcatEmbeddedServletContainer 和 TomcatWebServer 這兩個類來表示。
這個 Factory,也是做為一個 Bean 參與到Spring Boot 的啟動流程中。我們需要做的,就是在啟動的時候,定義這樣一個Bean,并「重寫」Factory 中可以拿到 Tomcat 實例的方法,拿到前面創建出來的 Tomcat 實例,即可完成應用的部署。
1.x 的方式如下:
- @Bean
- public EmbeddedServletContainerFactory servletContainerFactory() {
- return new TomcatEmbeddedServletContainerFactory() {
- protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
- Tomcat tomcat) {
- new File(tomcat.getServer().getCatalinaBase(), "webapps").mkdirs();
- try {
- Context context = tomcat.addWebapp("/test", "/home/test/sample.war"); // 這里是要部署的應用名稱和路徑
- context.setParentClassLoader(getClass().getClassLoader());
- } catch (Exception ex) {
- throw new IllegalStateException("Failed to add webapp", ex);
- }
- return super.getTomcatEmbeddedServletContainer(tomcat);
- }
- };
- }
2.x
- @Bean
- public ServletWebServerFactory servletContainerFactory() {
- return new TomcatServletWebServerFactory() {
- protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
- new File(tomcat.getServer().getCatalinaBase(), "hello").mkdirs();
- try {
- Context context =
- tomcat.addWebapp("/foo", "/home/test/sample.war");
- context.setParentClassLoader(getClass().getClassLoader());
- } catch (Exception ex) {
- throw new IllegalStateException("Failed to add webapp", ex);
- }
- return super.getTomcatWebServer(tomcat);
- };
- };
- }
當然,還有其它的方法也可以實現類似的目的。
比如,幾年前的一篇舊文,在分析 IDE里 Tomcat 的工作原理的時候,分析過 IDEA 里, Tomcat 是怎樣部署應用的。那個實現思路,是通過 Tomcat 注冊的 MBean,其中包含對于應用管理的MBean,對于嵌入式的 Tomcat,也依然放開了 MBean Server, 連接到上面就可以部署應用了。需要注意的一點,是嵌入式的 Tomcat,Host 的ObjectName,和獨立運行的并不一樣,需要注意,否則會導致部署失敗。
總結一下,嵌入式容器,也保留了獨立部署容器的管理和使用習慣,在啟動創建的過程中,可以獲取其容器實例進行操作。也可以通過對外暴露的 MBean Server 進行操作。
【本文為51CTO專欄作者“侯樹成”的原創稿件,轉載請通過作者微信公眾號『Tomcat那些事兒』獲取授權】