1、前言:
上篇文章講到部落客目前有兩種實作此需求的方案,但隻實作了一種操作較為繁瑣的:搬運jar包。
這篇文章我們來說一說,如何使用代碼做到動态切換中間件!!
讀前須知:“org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration”,這個由spring提供的類是根據某些條件建立中間件的Bean的核心類!!
spring所寫的條件為:@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class }),即當同時掃描到Tomcat和UpgradeProtocol這兩個類時,該注解所在的類生效,進而建立目标中間件的Bean。
2、代碼實作:
我們還是以tomcat和jetty切換來舉例,我們的項目嵌入的是jetty,然後需要項目打包後,在不改動代碼和pom檔案的情況下,可以動态切換到tomcat,并且使用maven插件進行項目執行jar包與依賴分離。
2.0、環境準備
我們建立一個springboot的項目,寫一個接口負責測試,然後排除内嵌的tomcat,導入jetty依賴,也就是說我們現在啟動是使用jetty來啟動的
這裡為什麼不将兩個中間件的依賴一起導入,或者是内嵌tomcat切換jetty呢。
第一是因為tomcat比jetty的優先級要高,如果在導入依賴的時候一起導入,那麼寫判斷就不起作用了,會預設使用tomcat
第二是使用這個方法的話,我們需要在切換時手動将目标中間件加入打好的libs檔案夾中,和上個方法不同的是,我們現在隻需要打一個包和檔案夾,需要切換時将目标中間件的依賴直接添加進libs檔案夾就可以,就是需要手動找到springboot在使用該中間件時的所有jar包(千萬不要隻找個spring-boot-starter-tomcat這樣的啟動依賴,這個依賴隻是告訴maven需要導入的那些實際依賴叫啥,是哪個版本的而已,打包後就沒用了,具體的原因可以去查一下springboot的啟動依賴原理),jetty實際需要的依賴太多,找起來很麻煩,是以就内嵌jetty,tomcat的實際依賴不多,比較好找。
jetty實際依賴(19個,下圖不一定截全,但是看得出來很多)
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnLxkTMkFWY0UzN0czYmJTY4IzYlRTNkBDNkZ2NiV2YjJzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
tomcat實際依賴(4個,是加上starter-tomcat依賴的)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除内嵌的tomcat-->
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
<!--Test依賴-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.3</version>
<!--使熱部署的devtools生效-->
<configuration>
<fork>true</fork>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
<layout>ZIP</layout>
<includes>
<include>
<groupId>non-exists</groupId>
<artifactId>non-exists</artifactId>
</include>
</includes>
</configuration>
</plugin>
<!--拷貝第三方依賴檔案到指定目錄-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!--target/libs是依賴jar包的輸出目錄,根據自己喜好配置-->
<outputDirectory>target/libs</outputDirectory>
<excludeTransitive>false</excludeTransitive>
<stripVersion>false</stripVersion>
<includeScope>runtime</includeScope>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2.1、首先,我們需要重寫spring所提供的EmbeddedWebServerFactoryCustomizerAutoConfiguration類,并且需要讓原本的類不生效
2.2、然後我們需要模仿原來的類重寫一下傳回的Bean
@AutoConfiguration // 這個注解可以會在某些項目中報錯,參照你們項目中spring原本的Em....AutoConfiguration類,上方是什麼注解就寫什麼注解
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class WebVesselAuto {
@Configuration(proxyBeanMethods = false)
@Conditional(WebJettyVesselJudge.class) //該注解的意思為當value為true時該類生效
public static class JettyWebServerFactoryCustomizerConfiguration {
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
2.3、綜上我們現在來建立負責判斷要使用哪個中間件的方法
我們先建立一個空的檔案夾(如果檔案夾中有檔案就說明需要切換中間件)
編寫判斷邏輯
//需要繼承Condition這個類,不然注解不生效
public class WebJettyVesselJudge implements Condition {
//有檔案傳回true 沒有檔案傳回false
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String[] fileName = getFileName("E:\\testWeb");
try {
return fileName != null;
} catch (NullPointerException e) {
return false;
}
}
//讀取檔案夾中的檔案
public static String[] getFileName(String path) {
File file = new File(path);
String[] fileName = file.list();
return fileName;
}
}
2.4、我們的代碼就寫完了,然後開始打包
打包前先clean一下,然後選擇package或者install都可以
看過上一章的朋友應該知道,我們使用插件分離了可執行jar包和項目依賴,項目的依賴都在libs這個檔案夾下
2.5、測試jetty
現在我們輸入指令進行測試,指令和上一章節的一樣
可以看到是jetty啟動的,再測試接口
目前來看我們的jetty啟動是沒有問題的
2.6、測試tomcat
現在我們去找到tomcat的四個依賴(部落客使用的是springboot2.7.3版本,其他版本的可能會不一樣)
将這四個依賴拷貝到libs檔案夾中
可以看到jetty的依賴還是在檔案夾中的,然後在空目錄testWeb檔案夾中随便添加一個檔案,用來告訴項目,我要切換中間件了,别建立jetty的Bean
然後傳回cmd指令視窗,重新輸入指令啟動
這時已經切換為tomcat了,我們再次測試接口,測通即可。
3、寫在最後
實際運用中,可以根據此方法做出很多改進,比如部署到伺服器後,建立一個目錄儲存目标中間件的依賴,使用shell腳本來判斷這個目錄有沒有檔案,有的話将目标中間件的jar包拷貝到libs中,然後使用相同指令啟動,便可做到隻需要檔案夾中放入需要的目标中間件的依賴就可以動态切換中間件。
如果有表述不清的地方,不明白為什麼這麼做或者其他問題的,可以先去看一下這篇文章奇葩需求第二彈:如何動态切換中間件(web容器)_Java Devin的部落格-CSDN部落格,或者有更好建議以及發現部落客文章中存在錯誤的,歡迎私信留言,部落客會積極改進。
最後說明,創作不易,若轉載請标明出處或原文連結!!!