0、场景说明
前几天,博主接到了迄今为止最为奇葩的一个需求,要求是在项目上线后,在不能改动任何原有代码(包括pom文件)的情况下,解决可以动态切换启动容器(就是tomcat、jetty等等)的问题,形式和方法不限(言外之意就是目前公司没人做过但是理论上应该可以,然后让你自己去琢磨),只能是jar包不能是war包,这个需求让原本只需要在pom文件改几行重新打包发布或者打个war包放在外置容器里启动的简单方式不复存在。
进入主题:为了能更有带入感,博主模拟一个场景,假如A是客户,B是开发人员,A啥也不懂,只有一个能简单操作电脑的运维,现在B把项目的jar包给了A,A的运维把项目部署在他们的服务器上然后java -jar 启动了,过了一段时间,A突然觉得tomcat不咋好用了,给他的运维说:你给我把这个web容器换成jetty,但是运维不会啊,pom文件已经打包进jar包了也没法改,现在呢运维就说是开发考虑的不全面,然后B做为乙方,就只能背锅优化了(B的心里一万匹***奔腾),所以要解决这个问题,就需要让A的运维在一些简单易懂的操作或不用操作的情况下将这个web容器给换为jetty。
好了,啰嗦了这么多,其实就一个目的,想让看到我本片文章的朋友们理解这个需求和需求背景,因为我也问过身边朋友,很多人不太理解这个需求。
1、梳理思路
首先,我们知道Spring boot(下面简称boot)是有自己内嵌的web容器,并且可以通过排除内嵌的容器,导入其他已经与boot做好适配的中间件依赖就可以做到切换容器启动,那么我们就需要了解以下几点:(1)、boot是如何将中间件的依赖注入到项目中的(boot自动化配置原理) (2)、boot加载容器的整体流程是怎样的(boot启动加载tomcat原理) 了解这两点后,我们就大概可以看出可以从哪入手去做这个需求了,上面的两点不了解的可自行百度,日后博主有时间也可能会发一篇自己的,要是发了会上链接。
思路1:从boot自动化配置可以假设如果可以将其他容器的依赖模仿自动化配置,原本项目是用内嵌的容器启动的,但是如果在项目启动进行自动化配置前加一层判断,判断是否除了原有的容器的依赖是否还有其他容器的依赖存在,如果有就用其他的,如果没有,就不做处理继续启动
思路2:boot在打包时会将项目依赖的jar位置写入classpath中,能否通过自定义classpath的指向来做到切换容器
2、实现过程
目前博主测试了第一种,理论上应该可以通过重写代码做到切换的,但是失败了,可能时逻辑上有问题,或者这个思路错了,所以我决定,先把第二种写下来,以防我日后忘记。
(博主将第一种方法的实现单独写了一篇文章–>https://blog.csdn.net/qq_51785096/article/details/127090376?spm=1001.2014.3001.5501)
首先,博主肝了一下加载boot加载容器流程的代码后发现EmbeddedWebServerFactoryCustomizerAutoConfiguration这个类是重点,不论是什么容器,都需要经过这个类去创建容器的Bean,因为代码很长,下方以tomcat为例
//解释一下上面几个注解(从上往下)
//该类为自动注入的类
@AutoConfiguration
//该类只有在项目为web项目时才会生效
@ConditionalOnWebApplication
//让被 @ConfigurationProperties 注解的类生效。
@EnableConfigurationProperties(ServerProperties.class)
//发现Tomcat.class和UpgradeProtocol.class的类时,该类生效(!!!重点)
@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
由此可见,boot判断需要创建哪个类的Bean时,是根据扫描到的容器依赖中的某几个类去判断的,也就是说,如果我们的容器已经适配了boot,那么完全可以通过替换已经打好jar包的项目中容器的依赖包去骗过这层代码,从而实现切换。
我们在打包时,可以通过maven提供的插件去选择jar包或是war包,但是项目所需的依赖和项目是在同一个jar包中的,这样就无法实现替换依赖包了。
不过,天无绝人之路,博主在度娘的帮助下成功发现maven同样也提供了可以将项目所需的所有依赖和可执行项目的jar包分开打包的插件,我们将他们分别打包,在需要切换容器时,把项目停下来,将依赖所在文件夹中的容器依赖包换成我们想换的容器的依赖,理论上在boot在重启项目,再次扫描依赖时判断一下几个可证明容器身份的类,就会为容器创建相对应的Bean,这样就成功切换了。
好了 分析需要做的事情之后,直接上代码
2.0、前期准备
首先我们需要一个Spring boot的项目
其次项目中包含一个测试用的简单的接口,如图
2.1、下面的代码是开启依赖的热部署以及将依赖包和项目的可执行jar包分离且输出至指定位置
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!--使热部署的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>
2.2、排除掉内嵌的tomcat
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</exclusion>
</exclusions>
</dependency>
2.3、找出启动容器的依赖(演示tomcat和jetty的切换)
首先我们要知道,pom文件中使用的boot的启动依赖stater,这个只是boot将该boot版本需要的该依赖版本统一了而已,所以spring-boot-stater-tomcat的依赖包对于目前的操作没有用
我们以tomcat中间件为例,先将中间件的依赖在pom文件中导入,然后运行项目进行测试,没有问题后,开始打包,打包完成后我们target目录中出现项目的可执行jar包及libs的文件夹
pom文件配置如下
<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>
<!--tomcat的启动依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<!--Test依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
打包前务必先在idea里面测试项目是否可以成功运行没有问题后再进行下面的操作
在target目录输入cmd进行测试
java -Dloader.path=(libs文件夹所在全路径),resources,lib -jar ***.jar
如图启动成功
我们将此时的libs文件夹拷贝一份,在外建一个tomcat的文件夹存入,现在的libs中就是与项目适配成功的依赖,并且是以tomcat中间件启动
随后我们将pom文件中tomcat的依赖全部删除,换为jetty的依赖
pom文件配置如下
<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>
<!--jetty依赖-->
<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>
打包前务必先在idea里面测试项目是否可以成功运行没有问题后再进行下面的操作
我们重新打包,打包及测试与示例的方法一样(注:记得要关闭上次命令窗口测试时正在运行的项目(ctrl + c)一次不行就多按几次、关闭命令窗口以及在Idea中clean一次,不然打包后的libs文件夹中还是会有之前tomcat的依赖)
在tomcat和jetty的命令窗口都测试成功后
我们现在只将内嵌的tomcat排除掉,不添加任何中间件的依赖
pom文件配置如下
<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>
<!--Test依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
和之前的操作一样,idea测试,测试成功后重新打包(此次不一样的地方在于没有中间件就不会出现启动容器成功的日志,如下图即为成功)
cmd窗口测试
与idea控制台输出的一样,即为成功
不要关闭命令窗口,往下看
2.4、测试动态切换中间件
先测试tomcat
java -Dloader.path=(tomcat的libs文件夹所在全路径),resources,lib -jar ***.jar
根据日志可观察到已经使用tomcat中间件启动成功
将项目停下来,再测试Jetty
java -Dloader.path=(jetty的libs文件夹所在全路径),resources,lib -jar ***.jar
根据日志可观察到已经使用Jetty中间件启动成功
3、写在最后
到这里动态切换中间件的方法说明结束,理论上其他已经与spring boot适配好的中间件都可以这样操作,后续博主可能会出一期操作教程发在哪到时候再看,发出来后会将链接更新在本篇文章中。
最后说明,创作不易,若转载请标明出处或原文链接!!!