☰
SpringBoot迁移宝兰德BES实战:从依赖冲突到类加载避坑指南
2026/10/5 1:35:11 网站建设 项目流程

信创改造这件事,没做过的人以为只是换个中间件、重新打个包,做过的人才知道,从SpringBoot内置Tomcat迁到宝兰德BES 9.5.5,整个过程就像把一套精装房里的定制家具搬进一套毛坯房的固定格局里——大部分东西能用,但总有几个尺寸对不上,得现场改。我最近刚交付完一个信创适配项目,项目本身是个基于SpringBoot 2.7的老服务,要跑在国产化环境里,选型定的是宝兰德BES 9.5.5。一开始我也以为就是pom里加个war插件、排除Tomcat、继承SpringBootServletInitializer三步搞定,结果从依赖冲突到日志丢失、从Classloader策略到数据源适配,前后折腾了两周,踩了不少网上资料里语焉不详的坑。

这篇文章就把整个迁移过程、依赖配置和避坑思路完整写出来,重点覆盖几类读者:正在做信创改造但还没定方案的、已经在BES上部署但遇到ClassNotFound或者日志不打印的、以及准备把SpringBoot 2.x/3.x项目迁到国产中间件的朋友。下面内容不是官方文档翻译,是我在实际项目里一条命令一条日志试出来的,尽量说人话,配置能直接抄。

1. 为什么SpringBoot部署到BES不是"换个容器"这么简单

1.1 先搞清楚BES在信创体系里的位置

宝兰德BES(BES Application Server)是国产Java EE应用服务器,对标的是WebLogic、WebSphere、Tomcat这类中间件。在信创技术栈里,它通常是替代国外商业中间件的老大难环节,因为上面跑的都是老业务系统,迁移风险高。但SpringBoot项目迁到BES,性质又不太一样——SpringBoot本身自带内嵌Tomcat,是"自带容器"的应用,而BES是"外部容器",两者叠加时不是简单的替换关系,而是两个运行时环境的融合问题。

以BES 9.5.5为例,它完整支持Java EE 8规范,底层提供Servlet容器、EJB容器、JMS等企业级能力。SpringBoot应用部署进去后,应用本身还是按SpringBoot的机制启动,只不过Servlet容器从内嵌Tomcat换成了BES。

1.2 两种启动模型的本质冲突

SpringBoot默认用java -jar启动,内嵌Tomcat随着应用一起启动,端口、线程池、Session策略都由application.yml控制。迁移到BES后,应用打成war包,由BES负责创建Servlet上下文和加载应用,这时有几个问题随之而来:

  • server.port配置会失效,对外端口由BES管理;如果需要上下文路径,优先用context-path;如果BES已经设置了应用上下文根,要以BES那边的设置为准,否则会出现404或者路径对不上。
  • 应用生命周期从"SpringBoot自己启动"变成"容器先启动,再回调SpringBoot的onStartup方法",所以必须继承SpringBootServletInitializer并重写configure方法。
  • SpringBoot的自动配置仍然生效,但它扫描的是war包里的类,如果Classloader策略不匹配,就会报ClassNotFound或者NoSuchMethodError。

换句话说,改pom只是让war包能被识别,真正决定成败的是运行时两种机制的协同。

1.3 版本选型必须先定下来:SpringBoot 2.x和3.x差别很大

这是我在项目启动前花了一整天确认的事情。BES 9.5.5基于Java EE 8,依赖的是javax.命名空间。SpringBoot 2.x也是javax.,兼容性天然好。但SpringBoot 3.x已经迁移到Jakarta EE规范,用的是jakarta.servlet.*命名空间,直接打成war丢给BES会报NoClassDefFoundError: jakarta/servlet/ServletContext。

如果项目是SpringBoot 3.x,迁移前需要先去宝兰德官方确认BES版本是否提供了Jakarta EE支持,或者考虑对BES 9.5.5对应的版本做适配。实际项目中,很多信创环境选型时仍以SpringBoot 2.x为主,2.6.x和2.7.x是安全区间。JDK和BES也有兼容关系,BES 9.5.5支持JDK 8和JDK 11,具体用哪个版本要在安装BES前确认,不然启动阶段就会出错。

提示:如果团队对版本没有强诉求,建议直接用SpringBoot 2.7.x + JDK8 + BES 9.5.5的组合,这是目前踩坑最少、社区案例最多的搭配。

2. pom.xml改造:从jar到war的依赖全清单

这一节是标题里写明的"附依赖配置",我直接把改造过程中最终稳定运行的pom样例贴出来,然后逐个说明每个部分为什么必须这么写。

2.1 改造前必须做的动作:锁定原依赖树

拿到一个老项目,先别急着改pom。我先跑了mvn dependency:tree -Dverbose,把项目现有的依赖树导出来,重点关注有没有spring-boot-starter-web、spring-boot-starter-tomcat、javax.servlet-api、javax.websocket以及其他和Servlet容器强相关的依赖。

这一步的价值在于:迁移过程中如果出现ClassNotFound,你至少能快速判断这个类是来自应用还是来自BES。我习惯保留一份迁移前的dependency tree,问题排查时直接在两个版本之间对比。

2.2 四个核心改造点

第一,packaging从jar改成war,让Maven产出war包。

第二,排除spring-boot-starter-web中内嵌的spring-boot-starter-tomcat。这一步有几种做法,比较推荐在starter-web里直接加exclusion,这样后续依赖方不需要关心是否误引入了Tomcat。

第三,启动类继承SpringBootServletInitializer,并重写configure方法。注意,如果项目里有多个配置类或者使用SpringApplicationBuilder自定义了启动逻辑,configure方法里的sources必须和主启动类保持一致。

第四,补充javax.servlet-api依赖,scope必须是provided。因为外部容器已经提供了Servlet API,如果作用域设为compile,运行期会出现jar冲突,具体表现是各种奇怪的NoSuchMethodError。

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>demo-bes</artifactId> <version>1.0.0</version> <packaging>war</packaging> <properties> <java.version>1.8</java.version> </properties> <dependencies> <!-- Web核心依赖,排除内嵌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> <!-- JDBC事务等基础能力 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jdbc</artifactId> </dependency> <!-- Servlet API,由BES提供,必须provided --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <!-- 数据源相关,HikariCP在SpringBoot 2.x默认集成 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- 可按需添加MyBatis、Redis、MQ等 --> </dependencies> <build> <finalName>demo-bes</finalName> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <mainClass>com.example.DemoApplication</mainClass> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <configuration> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </build>

启动类改造:

@SpringBootApplication public class DemoApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(DemoApplication.class); } public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }

2.3 排除内嵌Tomcat的三种方式与取舍

我整理一下实际操作中见过的三种方案:

方案做法适用场景风险
方案A在spring-boot-starter-web里直接排除starter-tomcat推荐,干净利落需要留意其他starter是否也传递引入了Tomcat
方案B将spring-boot-starter-tomcat声明为provided改动最小,但只是“编译期不打进去”,运行期仍可能被加载当BES/classloader策略不当时易出现容器类重复
方案C保留Tomcat,启动时禁用WebServer不适用于war部署,基本不推荐容易造成端口冲突

我最终采用的是方案A。方案B看着简单,但有个实际问题:如果BES的Classloader是“应用优先”模式,provided的Tomcat jar虽然没在war里,但JDK模块、BES lib目录里的类也可能被类加载器扫到,冲突面反而更大。

2.4 除了SpringBoot基础依赖,还要注意哪些jar

如果项目里用了WebSocket、JSP、JSTL、定时任务@Scheduled、Spring Security这些能力,要注意它们在不同容器下行为不一样:

  • WebSocket:spring-boot-starter-websocket默认用的是Tomcat的WebSocket实现,迁移后需要确认BES自带的WebSocket兼容性。我在项目里遇到Session并发推送偶发失效的问题,排查后确认是BES的WebSocket实现与Tomcat的Session管理策略不同,最终通过调整BES的连接器和Session超时参数解决。
  • JSP/JSTL:BES支持JSP,但JSTL的实现版本可能和应用里带的版本冲突,建议直接把JSTL依赖版本升级到和BES一致,或者干脆不发JSP,统一用模板引擎。
  • Spring Session:如果项目用了Redis做Session共享,这个相对独立,受容器影响较小,但要确保BES的HttpSession配置没有开启“持久化到本地”的选项,否则会出现两次会话写入的奇怪现象。

建议:每次调整pom后,执行mvn clean package -DskipTests,然后解压war包检查BOOT-INF/lib目录,确认没有多余的Tomcat相关jar,光靠IDE里的依赖树有时候是看不全的。

3. 打包、部署与上线配置:BES 9.5.5实操记录

3.1 首次启动BES与控制台部署war包

BES装好后,启动方式是在安装目录的bin下执行启动脚本。控制台的默认访问地址一般为http://服务器IP:8080/console,如果安装时指定过其他端口,以实际配置为准。首次登录会要求初始化管理员密码,正式授权需要在安装后导入license文件,否则部署几个应用后可能直接报授权不足,这一点建议提前在测试环境验证,免得正式上线当天被卡住。

部署war包的路径很简单:登录控制台后进入应用管理页面,上传war包,设置应用名称和上下文根。如果war包是2.7的SpringBoot,上传后BES会自动识别SpringBoot应用并触发onStartup,这里有个容易误操作的细节——如果应用名称和上下文根填错,会出现应用显示“已启动”,但访问路径怎么都404。排查时先看BES控制台的应用状态,再看日志里的context path。

3.2 必须确认的四个运行参数

第一个是JVM内存参数。在BES的domain启动配置中调整,通常在bin/目录下的domain脚本或控制台的服务器配置中。SpringBoot应用在BES里跑,堆内存建议不低于2G,如果是多用例部署,记得算好总内存,别让多个JVM进程互相抢资源。

第二个是上下文根。BES控制台在部署应用时可以指定上下文根,如果应用里配置了server.servlet.context-path,两者是叠加关系。我建议只在BES这一侧设置,把application.yml里的context-path留空,避免迁移后路径多了一层,联调时各种“少了一个前缀”的诡异问题。

第三个是Classloader策略。BES控制台提供“应用优先”和“容器优先”两种模式。SpringBoot应用建议选择应用优先,否则BES容器lib目录下自带的Spring版本、commons库版本有可能被优先加载,应用启动时直接报Bean冲突。

第四个是文件上传大小和HTTP会话超时。这些在BES的HTTP监听器配置中调整,和Tomcat的maxPostSize、sessionTimeout类似。不调的话,上线后大文件上传功能大概率会报413或连接被重置。

3.3 脚本化部署:不用每次打开控制台

如果发布频率高,建议配置BES的命令行部署脚本。BES安装目录下通常提供管理脚本,可以执行监听器创建、应用部署、启动、停止等操作。实际项目中我们写了一个简单的shell脚本,每次发布时执行:

#!/bin/bash BES_HOME=/opt/bes APP_NAME=demo-bes WAR_PATH=/opt/deploy/demo-bes.war # 停止应用 $BES_HOME/bin/stopserver.sh # 备份旧包 mv $BES_HOME/domains/domain1/applications/$APP_NAME \ $BES_HOME/backup/$APP_NAME_$(date +%Y%m%d%H%M%S) # 拷贝新包 cp $WAR_PATH $BES_HOME/domains/domain1/applications/ # 启动应用 $BES_HOME/bin/startserver.sh

这个脚本在生产时还能进一步改成远程执行或接入发布平台,核心思路是让BES的部署路径固定化,不要每次在控制台里点来点去,减少人为误操作。

3.4 配置文件外置与环境变量注入

信创环境里,测试环境、预发环境、生产环境的数据库地址和中间件地址往往不同,我不建议把生产配置打进war包。SpringBoot支持启动时指定外部配置,BES启动脚本里可以加上JVM参数,比如:

JAVA_OPTS="$JAVA_OPTS -Dspring.config.additional-location=/opt/bes-conf/application-prod.yml"

也可以用环境变量的方式:

export SPRING_PROFILES_ACTIVE=prod export SPRING_DATASOURCE_URL='jdbc:dm://10.0.0.5:5236' export SPRING_DATASOURCE_USERNAME='SYSDBA' export SPRING_DATASOURCE_PASSWORD='******'

这一步看起来简单,但很多迁移项目中途卡在“本地启动正常、部署到BES后连不上数据库”,就是因为环境变量没有加载到BES的JVM进程里。改完启动脚本后记得重启domain进程,而不是只重部署应用。

4. 迁移过程中绕不开的坑:真实踩坑与排查思路

4.1 内嵌Tomcat没排干净:ClassNotFoundException的定位链路

第一次打包部署到BES时,应用启动到一半报ClassNotFoundException: org.apache.catalina.startup.Bootstrap。这个类很明显来自Tomcat,说明war包里还是带上了内嵌Tomcat的相关jar。

我当时先看了pom,明明已经在starter-web里排除了starter-tomcat。后来用mvn dependency:tree查才发现,项目里另一个第三方依赖项传递引入了spring-boot-starter-tomcat,把排除逻辑绕过去了。处理办法是把那个第三方依赖也单独做一遍exclude,或者在Maven dependency management里强制排除。

这个坑的通用排查思路是:先把war包解压出来,在WEB-INF/lib下搜索catalina、tomcat相关jar,如果还有,查依赖树确认是谁引入的,然后精准排除。不要只排查自己写的pom,第三方传递依赖是重灾区。

4.2 SpringBoot 3.x的jakarta命名空间与BES 9.5.5的冲突

有个兄弟项目组拿了一个SpringBoot 3.0的项目迁到BES,直接报NoClassDefFoundError: jakarta/servlet/ServletContext。因为BES 9.5.5内部运行在Java EE 8规范上,提供的是javax.servlet.ServletContext,而SpringBoot 3.0里的Spring Web MVC已经编译到jakarta.servlet了。

这个问题不是改一行配置能解决的。要么把项目降级到SpringBoot 2.7.x,要么确认宝兰德是否有支持Jakarta EE的更高版本BES。最怕的是团队已经用了SpringBoot 3的新特性(比如AOT、新的HttpClient),降级成本很高,所以选型阶段一定要先对齐版本,不要等到做了一半才发现容器规范不匹配。

4.3 依赖冲突与Classloader策略调整

还有一次是应用启动后运行到某个业务功能时报NoSuchMethodError,同一个类在两个jar里存在,版本不一致。在Tomcat下可能没事,因为Tomcat的WebAppClassLoader对WEB-INF/lib的应用类优先,覆盖了容器类。但BES默认的Classloader策略可能不同,导致容器自带的旧库版本抢先被加载。

我在BES控制台把应用的Classloader策略修改为“应用优先”后,问题直接消失。如果你不想全局改,也可以通过在应用下添加bes-web.xml之类部署描述符,单独为当前应用指定类加载顺序。

这个经验很重要:迁移到BES后如果出现运行期的NoSuchMethodError或奇怪的AbstractMethodError,大概率不是代码问题,而是类加载优先级问题。

4.4 日志文件不输出:logback与BES日志框架的争夺

迁移后有个典型现象:应用能启动,控制台也有输出,但自己项目的日志文件里什么都没有。原因是BES自带日志框架和logback冲突,应用里的logback-spring.xml没有生效,或者被BES的日志配置覆盖了。

排查时可以按这几个顺序走:第一,确认logback-spring.xml放在src/main/resources根目录下;第二,检查BES是否有全局日志配置覆盖了应用的日志实现;第三,在启动日志里搜索“Logging system”相关的输出,看SpringBoot是否识别到了logback配置。

如果始终不生效,可以在启动参数上强制指定日志配置位置,比如-Dlogging.config=/opt/bes-conf/logback-spring.xml,这样最稳妥。生产环境我们就是直接这么干的,配置文件外置比打进war包好管理得多,也方便后续动态调整日志级别。

4.5 数据源和连接池:直接用HikariCP还是切到BES数据源

SpringBoot 2.x默认的HikariCP在BES下工作基本没问题,只要Classloader策略正确,数据源照常初始化。但要注意一点:如果应用里同时配置了JNDI数据源和SpringBoot数据源,BES的JNDI数据源优先级可能会高于应用配置,造成连错库的情况。

我的建议是迁移初期不要引入JNDI数据源,继续使用SpringBoot自带的HikariCP配置,少一个变量就少一个坑。等整个链路稳定后,如果运维强制要求数据库连接资源由BES统一管理,再逐步切JNDI数据源。

5. 信创数据库与中间件适配:达梦、金仓与Redis

5.1 达梦数据库JDBC接入与连接参数

信创环境下遇到最多的数据库是达梦(DM8),SpringBoot连接达梦的配置如下:

spring.datasource.driver-class-name=dm.jdbc.driver.DmDriver spring.datasource.url=jdbc:dm://10.0.0.10:5236?schema=TESTDB spring.datasource.username=SYSDBA spring.datasource.password=123456

需要把达梦的JDBC驱动jar放进项目的lib或者推到私有仓库。如果项目的打包方式之前是jar,驱动jar和其他依赖一样打进BOOT-INF/lib就行;如果改成war部署到BES,直接放在WEB-INF/lib里也能被加载。

连接达梦容易出的问题在于schema大小写和权限。达梦默认对大小写敏感,如果建表时用了双引号,后面查询不带双引号就找不到表。建议在迁移前统一约定:建表对象名全部使用大写,或干脆关闭达梦的大小写敏感配置。这个事不解决的,应用启动后跑几条SQL就报表或列不存在,排查起来特别费劲。

5.2 MyBatis/MyBatis-Plus的方言适配

如果项目用的是MyBatis-Plus,需要确认分页插件是否支持达梦。MyBatis-Plus自带的DbType.DM可以支持达梦,分页SQL会生成对应的方言,基本不需要改代码。但如果项目里用了自研的XML SQL,里面可能写了LIMIT ? OFFSET ?这类MySQL方言,迁到达梦后要改成达梦的LIMIT ? OFFSET ?写法——达梦本身兼容这个语法,但如果用了Oracle模式,就要用ROWNUM。所以先确认达梦的兼容模式是MySQL模式还是Oracle模式,这个要和DBA确认清楚。

金仓(KingbaseES)的驱动是com.kingbase8.Driver,URL格式为jdbc:kingbase8://host:54321/dbname,整体适配和达梦类似。关键是项目里如果用了数据库特定的函数、JSON字段操作,需要在迁移前做一轮SQL扫描,而不是等上线了再被用户报障。

5.3 Redis、MQ等中间件的连接验证

信创设备上的Redis、Kafka、RabbitMQ可能也换成了国产分支,但客户端协议基本兼容。迁移后最需要确认的是网络连通性和安全设置,应用部署到BES后运行在独立JVM里,网络策略、防火墙和之前在本地开发环境完全不同。

我在项目中遇到过一次Redis连接超时,本地、测试环境都正常,上了信创环境就偶发超时。最后发现是Redis服务端启用了保护模式,只允许本机访问,而应用部署的BES服务器和Redis不是一台机器。这类问题排查时用telnet或nc先做TCP层探测,能省很多时间。

5.4 SQL方言兼容性检查

信创数据库改造中,SQL兼容性是最耗时的环节之一。MySQL的group_concat、find_in_set、insert ... on duplicate key update这些函数在达梦里都不一定直接支持,需要改写。实际项目中我们专门拆了一个SQL工单出来,把代码里的SQL、XML里的SQL、存储过程里的SQL全部过了一遍,平均每个业务模块都要微调几条SQL。这个时间成本要提前算进项目计划里去,不要天真地以为换个Driver就能无缝跑起来。

6. 上线前验证清单与回滚预案

6.1 功能验证维度

上线前我习惯按下面几个维度拉一张验证表:

验证项验证内容通过标准
启动验证应用在BES上能正常启动、控制台无异常堆栈启动时间、日志无ERROR
登录认证单点登录、Session创建与销毁、登录超时登录登出正常,会话状态一致
核心业务业务主流程(下单、审批、查询等)数据正确,无SQL报错
文件上传下载上传大小限制、下载响应头、文件名编码大小、中文名、断点续传正常
定时任务@Scheduled任务是否在BES下正常触发触发时间、执行结果正确
第三方集成Redis/MQ/WebService等接口调用连通性、超时、异常处理正常
安全性Actuator端点、heapdump接口、默认端口权限安全扫描无高危项

SpringBoot Actuator的heapdump端点在生产环境建议直接关闭,信创验收时安全扫描很容易扫出HeapDump敏感信息泄露类的高危漏洞,management.endpoints.web.exposure.include不要默认暴露所有端点。

6.2 性能基线与观察指标

迁移到BES后,应用的性能特征会发生变化,不能简单认为和Tomcat一样。我在项目里用压测工具跑了三组对比:单用户进行链路冒烟、10并发基本业务、50并发混合业务。重点关注指标是接口平均响应时间、99分位延迟、GC频率和数据库连接池使用率。

BES环境下的一个常见问题是,若JVM参数沿用之前的默认值,在并发上来后Young GC暴增。解决方法是把BES的JVM堆内存、元空间大小显式配置,比如-Xms2g -Xmx2g -XX:MaxMetaspaceSize=512m,并搭配G1收集器。这块可以按项目实际负载微调。

6.3 回滚与灰度部署策略

信创改造上线最怕出大问题之后没法快速回滚。war包回滚的逻辑很简单:保留上一版war包,停应用、替换war包、重启。但如果数据库表结构已经变更过,回滚就不仅仅是替包的事,得评估数据库脚本能不能安全回退。建议每次发布前把数据库变更脚本和版本号绑定,做好降级预案。

另外一个可行策略是在BES前加一层负载均衡,先让一台服务器部署新版本,接入少量流量验证稳定后,再滚动替换其他节点。这种方式比一次性全量替换安全得多,实际项目中我们也是这样操作的。

写在最后的一点经验

这套迁移做完后我最大的感受是:信创改造里最费时间的不是改配置,而是建立“容器差异”的意识。很多坑在Tomcat里不会出现,因为Tomcat对应用极其迁就,几乎怎么折腾都行;BES作为企业级中间件,默认配置更保守,做了一些约束,应用自然就更敏感。团队里如果有人熟悉BES的Classloader策略和部署模型,整个迁移能少走一半弯路。

如果你正要开始做类似迁移,建议先把SpringBoot和BES的版本兼容矩阵锁死,再把依赖树里和Servlet相关的jar清理干净,最后才轮得到业务功能验证。顺序反了,你会被各种疑难杂症折磨到怀疑人生。希望这篇实战记录能帮各位省下一点排查时间,也欢迎在评论区分享你在信创迁移中踩到的其他怪坑。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询