如何給Spring Boot 的嵌入式 Tomcat 部署多個應用?
本文首發於微信公眾號「 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,和獨立執行的並不一樣,需要注意,否則會導致部署失敗。
如有需要,可以參考這兩篇:
你一定不知道IDE裡的Tomcat是怎麼工作的! mp.weixin.qq.com 你瞭解JMX在Tomcat的應用嗎? mp.weixin.qq.com總結一下,嵌入式容器,也保留了獨立部署容器的管理和使用習慣,在啟動建立的過程中,可以獲取其容器例項進行操作。也可以通過對外暴露的 MBean Server 進行操作。 條條大路通廣州!