ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot应用迁移到BES 9.5.5信创中间件完整改造指南

SpringBoot应用迁移到BES 9.5.5信创中间件完整改造指南 先说一个我自己的真实经历一个跑得好好的SpringBoot 2.7服务原本依赖内嵌Tomcat直接java -jar启动结果信创改造要求替换成国产中间件宝兰德BES 9.5.5。我当时以为“不就是换个地方部署嘛”结果从jar包改成war包、从依赖处理到数据源配置前前后后踩了一整周的坑。这篇文章就把涉及的依赖配置、打包方式、启动类改造和常见的运行时问题完整写出来给后面接手同样任务的同学少走点弯路。1. 先把一个关键认知掰正BES不是给SpringBoot“跑jar”的它接管的是war包很多第一次接触宝兰德BES的Java开发第一反应是“我项目是SpringBoot直接拿BES装一个运行环境然后把jar丢进去不就行了”。这个想法非常危险。BES 9.5.5作为一个完整的Java EE应用服务器最标准的应用部署单元是war包它通过自身的Web容器来加载、解析和管理应用。SpringBoot默认的内嵌Tomcat或者更准确地说SpringBoot那种java -jar直接启动的模式在BES上并不是不能跑但会绕开BES的部署治理体系也容易在服务管理、日志采集、会话保持这些环节断链。我这次迁移的目标很明确让SpringBoot应用真正“跑在BES内部”而不是在BES旁边再起一个进程。要实现这一点最初要改的就是部署形态。1.1 什么是“中间件替换”它到底替换了什么日常开发里说“用Tomcat部署”时大多数时候指的是“Servlet容器”它只负责处理Servlet、JSP、静态资源这些Web层的东西。宝兰德BES这类国产应用服务器不仅要处理Web层还提供了完整的Java EE规范支持比如EJB、JMS、数据源管理、集群能力。信创改造中的“中间件替换”本质上是把项目原来依赖的Tomcat、Jetty或者WebLogic这类第三方组件换成BES这个国产中间件而应用代码本身尽量保持不动。但SpringBoot和传统Java EE项目的最大区别在于它默认使用了“内嵌容器 可执行jar”的方式分发应用。这意味着Tomcat不是部署在外面的而是被打包在jar里、由SpringBoot启动时动态创建。要让BES接管就得把容器的职责“外移”也就是回到传统的应用部署模型外部容器负责Web服务应用以war包形式发布。1.2 哪些SpringBoot项目不建议硬迁到BES不是所有SpringBoot项目都适合一次到位迁移到BES 9.5.5。我这次踩完坑之后的最大体会是迁移之前先给项目做个“容器依赖体检”。如果代码里有以下这些情况需要先改造再迁直接引用内嵌Tomcat的类比如org.apache.catalina.*、org.apache.tomcat.*自定义了TomcatServletWebServerFactory、TomcatConnectorCustomizer这类容器定制Bean项目是SpringBoot 3.x并且用到了jakarta.servlet命名空间的较新API应用里配置了server.port、server.tomcat.xxx等容器级配置并且对端口有强依赖用到了WebSocket、JMX等对底层容器实现依赖较强的组件没有做标准API封装。如果你的项目在上述范围里不要急先做代码层适配然后再迁。否则就算war包能部署上去运行期也会频繁冒出各种和容器实现有关的异常。2. 折腾前先看版本BES 9.5.5、SpringBoot版本和JDK怎么选才不吵架版本选型这个事一开始是最容易被忽略、后期却最折腾的。我原本图省事直接拿SpringBoot 3.2的项目去迁结果发现BES 9.5.5的容器协议栈对jakarta.*命名空间的支持并不像我们期望的那样“无缝”一些Servlet相关API注册完全对不上。后来老老实实把项目降到SpringBoot 2.7.13才把主要问题解决掉。2.1 Servlet/Jakarta规范兼容性对照SpringBoot 2.x系列底层用的是javax.servletAPISpringBoot 3.x则切换到了jakarta.servletAPI。BES 9.5.5这套应用服务器按我的实测和查阅到的资料更贴合的是Java EE 8时代的规范体系也就是javax.servlet.*这一套。所以用SpringBoot 2.7.x JDK8/JDK11组合是最稳的路线。我整理了一个兼容性判断表可以根据项目现状对号入座SpringBoot版本Servlet命名空间BES 9.5.5部署实测情况建议2.5.xjavax.servlet能部署但部分SpringBoot高版本配置类不兼容老系统可保留2.7.xjavax.servlet最稳定和BES的Servlet 4.0/JSP规范匹配良好首选3.0.xjakarta.servlet容易出现Servlet注册、日志、自动配置等多个兼容性问题不建议直接迁先降级如果你手上的业务系统因为某些原因必须用SpringBoot 3.x那我的建议是先单独做一轮“基础设施适配”确认BES版本是否已经支持Jakarta EE 9规范。不要把这个判断寄托在“SpringBoot会自动适配”上中间件和应用框架之间是双向匹配关系不是单方面兼容。2.2 JDK版本选择要注意的细节BES 9.5.5对JDK版本也有自己的要求。我在本地Windows环境测试时用的JDK 8后来测试环境切到JDK 11也能正常跑。但如果你用的是JDK 17甚至更高建议先详细确认BES自带的字节码增强、热部署和集群服务是否支持。不要因为SpringBoot 2.7支持JDK 17就觉得BES也一定支持。另外安装BES时最好使用中间件厂商提供的JDK适配列表或者用安装包默认配套的JDK。我自己遇到过一种情况BES启动脚本里指定的Java路径和我应用编译用的JDK版本不同导致war部署后在运行期出现UnsupportedClassVersionError。这个问题很隐蔽因为编译阶段完全正常到正式加载应用时才会爆炸。3. 配置清单来了pom依赖、启动类、打包插件的完整改法这一节是全文的核心依赖配置可以直接抄。我以一个常规的SpringBoot 2.7.13项目为例改造目标是打包成war、剔除内嵌Tomcat、能被BES 9.5.5正常加载。3.1 把packaging改成war并排除内嵌Tomcat第一步是修改pom.xml中的打包方式。原来是jar这里必须改成wargroupIdcom.example/groupId artifactIddemo-bes/artifactId version1.0.0/version packagingwar/packaging第二步是处理spring-boot-starter-web依赖。这个starter默认会带进来内嵌Tomcat如果不排除打出来的war包会把Tomcat的类也带进去部署到BES后容易出现“Tomcat类和应用服务器类打架”的情况。正确的做法是把它排除掉dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency3.2 手动补充Servlet API依赖scope必须是provided排除掉内嵌Tomcat之后编译阶段会缺少Servlet相关API因为原来这部分的类由tomcat-embed-core提供。此时需要显式引入Java EE规范下的Servlet API同时必须设置为provided表示这个依赖最终由外部容器提供不会打进war包dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency这一步的位置很重要。如果你用的是SpringBoot 3.x则对应的是jakarta.servlet-api。但前面讲过BES 9.5.5更适配javax体系所以还是建议回到SpringBoot 2.7.x。3.3 启动类改造继承SpringBootServletInitializer原来普通的SpringBoot启动类只需要SpringBootApplication注解加main方法。部署到外部容器后为了能让BES在启动Web应用时正确触发Spring容器的初始化启动类必须继承SpringBootServletInitializer并重写configure方法import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.builder.SpringApplicationBuilder; import org.springframework.boot.web.servlet.support.SpringBootServletInitializer; SpringBootApplication public class DemoBesApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(DemoBesApplication.class); } public static void main(String[] args) { SpringApplication.run(DemoBesApplication.class, args); } }如果项目里有多个SpringBootConfiguration或自定义配置类作为启动入口记得在sources里指定正确的那个。否则BES能启动但Spring容器里加载不到你的业务Bean会表现出一堆“注入失败”的假象。3.4 spring-boot-maven-plugin到底要不要管外部容器部署场景下spring-boot-maven-plugin已经不负责生成可执行jar了。我的习惯是显式写出来但关闭repackage功能避免它对war包做二次包装build finalNamedemo-bes/finalName plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration skiptrue/skip /configuration /plugin /plugins /build如果你希望这个war包既能部署到BES又能在本地用java -jar方便调试那可以让repackage开着并给可执行war加一个classifier。但就信创生产环境而言部署到BES的war包越“干净”越好混乱的嵌套结构有时候会触发很奇怪的TLD扫描问题。3.5 一个完整的最小化配置贴在这里最后把完整的pom关键片段汇总如下你可以根据自己的项目结构调整parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.13/version /parent properties java.version1.8/java.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId exclusions exclusion groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId /exclusion /exclusions /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- 按需补充其他业务依赖 -- /dependencies build finalNamedemo-bes/finalName plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId configuration skiptrue/skip /configuration /plugin /plugins /build4. 一个个真实踩过的坑从启动失败到静态资源404的排查现场依赖配好了、war包打出来了不代表就万事大吉。这一章我把迁移过程中亲测遇到的高频问题按“现象-原因-解决”梳理出来很多坑不是看一眼代码就能定位的。4.1 部署后启动卡死或直接启动失败先确认classloader模式现象是war包在BES管理控制台部署后应用状态一直在“启动中”点日志也没有像样的报错最后Supervise超时。这个问题我排查了很久最后定位到是BES的classloader加载策略。应用服务器为了让不同应用之间的依赖互不干扰默认有一套“父类加载器优先”的逻辑。但SpringBoot的内部结构比较特殊它有一堆spring-boot-loader和自动配置类。如果外部容器的父加载器优先加载了某些通用类容易和war包WEB-INF/lib下的同名类产生不可预见的冲突。解决方式是在BES的管理控制台里针对这个应用把classloader模式调整成“应用优先”或“parent-last”。其他应用服务器也有类似概念比如Tomcat里的delegatefalse。改完后重启应用类加载才算真正正常。4.2 SLF4J日志冲突LoggerFactory is not a Logback LoggerContext这是外部容器部署SpringBoot项目的一个经典报错。war包里的应用用的是LogbackBES自带的日志框架可能也绑定了SLF4J于是运行时出现SLF4J: Class path contains multiple SLF4J bindings. LoggerFactory is not a Logback LoggerContext but it could be due to a known logback jar conflict我的处理思路分三步。第一步确认war包WEB-INF/lib下不要重复打入多余的SLF4J绑定实现第二步在logback.xml里显式指定输出策略第三步利用前面说的“应用优先”classloader模式让应用自带的日志实现不被BES父加载器覆盖。如果应用服务器自带的日志框架确实无法通过classloader隔离可以考虑让项目改为使用中间件提供的日志适配器。这个改动量较大一般放到二期优化再做首要目标是先让应用稳定启动。4.3 端口占用或应用端口不生效server.port在BES里说了不算原来内嵌Tomcat启动时应用自己监听8080端口。现在部署到BES8080很可能已经是BES自己用的HTTP端口。我遇到过两种情况情况一应用里保留了server.port8080的配置结果SpringBoot尝试再起一个Web服务端口和BES端口冲突启动报Address already in use。情况二改动server.port后应用由BES接管实际访问端口却也没按预期变化。原因很简单外部容器模式下端口、线程池、连接超时等Web服务器配置都由BES统一管理SpringBoot层面的server.tomcat.*配置大多失效。正确做法是把server.port去掉端口由BES统一规划。如果你需要多个应用部署在同一个BES实例上一般通过不同的上下文路径context path或虚拟主机来区分。4.4 war包名变成了访问路径上下文路径的默认规则另外一个高频问题是接口能通但前端页面打开全是404或者API路径总是多了个前缀。这是因为war包部署到BES后应用默认上下文路径就是war包文件名。比如demo-bes.war部署后访问入口就是http://localhost:8080/demo-bes/如果你的前端资源里的路径写死了/static/xxx.js自然会因为少了/demo-bes前缀导致404。解决方式有这么几种接受这个上下文路径前端资源、nginx或网关转发时统一加上前缀在BES控制台部署应用时手动指定上下文路径为/这样就等价于原Tomcat根路径访问如果是独立部署、没有网关更推荐显式设置上下文路径并统一维护。因为生产环境一般前面还有nginx或网关我建议不要强行把它设成/否则多个应用在同一个BES下会冲突。4.5 静态资源和JSP的支持问题SpringBoot默认对JSP的支持并不好但BES这类应用服务器天然具备JSP解析能力。如果你项目里混合了静态资源和少量JSP页面要注意JSP文件的位置。使用war包部署时JSP页面应该放在src/main/webapp/WEB-INF/views下而不是放在src/main/resources下。否则war包内部结构不会把JSP放在正确的位置BES解析时找不到对应视图。同时如果确实用到JSPpom里需要补上JSTL相关依赖dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency还有一个小细节SpringBoot打成war包后默认静态资源映射的优先级可能变。遇到原来能访问迁移后CSS/JS全部404的情况先看看访问路径里有没有带上上下文路径再检查WebMvcConfigurer里是否自定义了资源映射。4.6 数据源该放应用内还是JNDISpringBoot项目一般直接在application.yml里配数据源spring: datasource: url: jdbc:mysql://localhost:3306/demo username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver这种配置方式在BES下依然能用因为数据源本质上就在应用内创建和应用服务器无关。但信创环境下我建议尽量将数据源统一收口到BES中间件管理也就是用JNDI方式。这样数据库连接数、账号密码策略、监控都能统一维护切换环境时也不需要重新打war包。实现步骤是先在BES控制台配置数据源然后在SpringBoot里通过spring.datasource.jndi-name引用spring: datasource: jndi-name: java:comp/env/jdbc/demo-bes要注意的是一旦配置了jndi-name原来url、username、password等参数就不需要了。如果两者同时存在不同版本的BES下行为可能不一致容易导致数据源初始化失败。4.7 那些依赖内嵌Tomcat的代码必须提前改写这个坑技术含量不高但特别隐蔽。有些项目为了满足特定需求会注入Tomcat的底层对象比如通过ServletContext获取TomcatSessionManager或者在网上抄了一段自定义TomcatConnectorCustomizer用于调整keep-alive参数。这些代码在java -jar模式下跑得很好但到了BES里底层容器根本不是Tomcat代码一执行就抛ClassNotFoundException或NoClassDefFoundError。解决办法是全局搜索代码里和catalina、tomcat、Tomcat相关的包名逐步替换成标准Servlet API或Spring Cloud Gateway、负载均衡器层面的配置。5. 上线前用这份自检表再扫一遍迁移接近尾声后我习惯用一张自检表做最终确认。这张表也是我后续帮其他项目做中间件替换时的固定动作强烈建议打印出来逐项打勾检查项检查点通过标准打包形态pom.xml中packagingwar不是jar或pom内嵌容器spring-boot-starter-tomcat是否排除依赖树中没有tomcat-embed-coreServlet APIjavax.servlet-api是否为provided最终war包WEB-INF/lib下没有servlet-api启动类继承SpringBootServletInitializer并重写configureBES日志能看到Spring容器启动成功classloader应用优先/parent-last不再出现SLF4J多绑定或ClassCastExceptioncontextPath前端/接口访问路径统一静态资源、接口前缀、网关转发一致JSP位置src/main/webapp下且依赖完整视图能正常渲染Web容器Bean无TomcatServletWebServerFactory等自定义Bean代码扫描无相关引用数据源JNDI或应用内数据源单一模式数据库连接建立成功日志启动日志、业务日志正常输出无SLF4J警告堆栈集群会话session实现Serializable多节点登录状态正常启动方式不再使用java -jarBES管理控制台完成启动/停止5.1 部署前的备份和回滚设计这一点看起来是老生常谈但中间件替换过程中最容易忘记。war包部署到BES后如果临时需要回滚不是简简单单把老jar再跑起来就行。我目前的习惯是把目前生产环境的可执行jar完整留存同时保留一份SpringBoot自动配置快照也就是spring-configuration-metadata这类信息便于比对迁移前后的配置项差异。BES上部署的应用回滚时直接通过管理控制台上传旧版本war包即可。整个过程要提前写进变更方案里不然上线日一到大家手忙脚乱。5.2 压测时重点盯哪几个指标中间件替换后仅仅功能跑通不算完。我在本地跑过一轮简单的JMeter压测重点看的是以下三个方向吞吐量和响应时间变化由于Servlet容器实现不同SpringBoot应用从Tomcat迁移到BES后TPS可能会有10%-20%的波动需要根据业务指标决定是否需要调BES线程池、连接器参数。内存占用BES作为完整应用服务器本身常驻内存高于内嵌Tomcat。压测时观察堆内存和元空间是否存在持续上涨。连接池监控无论应用内数据源还是JNDI数据源长期压测下连接池回收是否正常是否能及时处理连接泄漏。如果压测阶段发现指标和之前差异过大第一反应不是去改业务代码而是先看BES的配置参数比如最大线程数、acceptCount、连接超时这些在Tomcat里可能存在application.yml现在都统一挪到BES的管理控制台或系统配置里了。6. 写在最后的几点经验迁移到BES 9.5.5这个事本质上不是“换部署工具”而是“变更运行容器”。SpringBoot项目最大的特性就是内嵌和自包含要把它改造成能被外部应用服务器托管最核心的动作是把容器的权利交出去端口交给中间件、类加载交给中间件、日志框架尽量适配中间件。我个人在实际项目里踩得最深的坑是低估了classloader隔离对SpringBoot自动配置的影响。很多奇怪问题比如Bean重复创建、配置属性读不到、定时任务突然不触发最后都指向“某个类被父加载器加载了”。所以第二次做类似迁移时我第一件事就是先把classloader模式调好再开始折腾依赖和代码。后续如果有条件可以考虑把BES集群部署和当前项目的会话保持一并做起来。这个扩展步骤同样不复杂但前提是应用层先保证session等对象可序列化并且不再使用任何Tomcat私有特性。把这些基础打牢再做集群配置就顺理成章了。
返回列表