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个,下图不一定截全,但是看得出来很多)
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的博客-优快云博客,或者有更好建议以及发现博主文章中存在错误的,欢迎私信留言,博主会积极改进。
最后说明,创作不易,若转载请标明出处或原文链接!!!