如何給Spring Boot 的嵌入式 Tomcat 部署多個(gè)應(yīng)用?
Spring Boot 的應(yīng)用,大都有這樣的特別,你在添加了依賴之后,即使是 Web 應(yīng)用,最終也可以通過 JAR 的形式運(yùn)行,具體依賴的容器環(huán)境,則通過嵌入式的形式隱式的使用。
而像這些環(huán)境,Spring 的配置等,更多的隱藏在 Spring Boot 的內(nèi)部,開發(fā)者可以更多的專注于「業(yè)務(wù)邏輯」的開發(fā)。
「解放了雙手」的時(shí)候,話說回來,某些時(shí)候,也是有一些弊端的。比如像之前通過 WAR 文件的形式獨(dú)立部署時(shí),可以在容器內(nèi)再額外部署一些「監(jiān)控」應(yīng)用,來觀察容器的情況,應(yīng)用的請求情況等,這些內(nèi)容在嵌入式的時(shí)候,就有些辦不從心了。
那對于 習(xí)慣了 Spring Boot 的 JAR 文件便捷運(yùn)行的用戶,有沒有辦法,能在保留 JAR 使用習(xí)慣的前提下,又能部署其他應(yīng)用,來滿足獨(dú)立容器部署的形式和使用習(xí)慣呢?
答案是有的。魚和熊掌,也可得兼。 后面我們會以嵌入式的 Tomcat 為例,來說明具體的實(shí)現(xiàn)方式。
首先,我們需要認(rèn)識這一點(diǎn),對于嵌入式的容器,他本質(zhì)上依然還是容器,保留了容器的絕大數(shù)內(nèi)容。所以,一些獨(dú)立部署時(shí)的風(fēng)格,接口也依然可以使用。
不熟悉 Spring Boot 內(nèi) Tomcat 工作原理的讀者,可以參考這幾篇舊文:
- Tomcat 是怎樣處理 SpringBoot應(yīng)用的?
- Tomcat 中 的可插拔以及 SCI 的實(shí)現(xiàn)原理
- 如何開發(fā)自己的 Spring Boot Starter
我們前面說,嵌入式容器,也還是容器,所以我們只要「拿到」這個(gè)容器,就可以對其進(jìn)行操作了。
舊文里我們提過, Spring Boot 內(nèi)的嵌入式 Tomcat,是自己 new 了一個(gè)Tomcat 實(shí)例出來,再把應(yīng)用做為 Context 部署進(jìn)去。我們要想部署其他的應(yīng)用,也照著「葫蘆」拿到 這個(gè)實(shí)例,部署應(yīng)用。
Spring Boot 內(nèi),由于要支持各種 Servlet 容器,所以統(tǒng)一進(jìn)行了抽象了創(chuàng)建容器的Factory,在 Spring Boot 1.x 和 2.x分別由
EmbeddedServletContainerFactory 和 ServletWebServerFactory 這兩個(gè)接口表示。 而對應(yīng)的工廠里創(chuàng)建出來的容器對象,在 1.x 和 2.x 中,分別由TomcatEmbeddedServletContainer 和 TomcatWebServer 這兩個(gè)類來表示。
這個(gè) Factory,也是做為一個(gè) Bean 參與到Spring Boot 的啟動流程中。我們需要做的,就是在啟動的時(shí)候,定義這樣一個(gè)Bean,并「重寫」Factory 中可以拿到 Tomcat 實(shí)例的方法,拿到前面創(chuàng)建出來的 Tomcat 實(shí)例,即可完成應(yīng)用的部署。
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"); // 這里是要部署的應(yīng)用名稱和路徑
- 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);
- };
- };
- }
當(dāng)然,還有其它的方法也可以實(shí)現(xiàn)類似的目的。
比如,幾年前的一篇舊文,在分析 IDE里 Tomcat 的工作原理的時(shí)候,分析過 IDEA 里, Tomcat 是怎樣部署應(yīng)用的。那個(gè)實(shí)現(xiàn)思路,是通過 Tomcat 注冊的 MBean,其中包含對于應(yīng)用管理的MBean,對于嵌入式的 Tomcat,也依然放開了 MBean Server, 連接到上面就可以部署應(yīng)用了。需要注意的一點(diǎn),是嵌入式的 Tomcat,Host 的ObjectName,和獨(dú)立運(yùn)行的并不一樣,需要注意,否則會導(dǎo)致部署失敗。
總結(jié)一下,嵌入式容器,也保留了獨(dú)立部署容器的管理和使用習(xí)慣,在啟動創(chuàng)建的過程中,可以獲取其容器實(shí)例進(jìn)行操作。也可以通過對外暴露的 MBean Server 進(jìn)行操作。
【本文為51CTO專欄作者“侯樹成”的原創(chuàng)稿件,轉(zhuǎn)載請通過作者微信公眾號『Tomcat那些事兒』獲取授權(quán)】