1. 项目概述:为什么Spring Boot项目要“换掉Tomcat”,偏偏选东方通TongWeb?
信创这个词,这两年在政企、金融、能源这些行业的技术会议上,几乎每场必提。但很多人一听到“信创适配”,第一反应还是——又要改代码?又要重测?又要加班?其实真没那么吓人,尤其当你用的是Spring Boot这种高度抽象的框架时,底层Servlet容器的切换,本质上是一次“拔插式”替换,不是推倒重来。关键在于:你得知道插哪里、怎么插、插完怎么验。而东方通TongWeb,就是国产中间件里目前落地最稳、文档最全、生态对接最成熟的那一款。
我去年接手一个省级医保平台的二期改造,原系统是Spring Boot 2.6.x + Tomcat 9,要求半年内完成信创环境迁移,目标运行环境是麒麟V10 + 飞腾2000处理器 + 东方通TongWeb V7.0。当时团队里有位老哥直接说:“Spring Boot打包成WAR丢到TongWeb里不就完了?”结果部署上去,首页404,日志里全是java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener。后来才发现,他连TongWeb的JDK版本要求都没看——TongWeb V7.0官方只支持JDK 8u261及以上或JDK 11,而他本地用的是JDK 17,连启动都报错。这事儿让我意识到:信创不是换个名字喊口号,它是一整套兼容性链条,从JVM、类加载机制、线程模型到管理控制台的交互逻辑,全都得重新对齐。
所以这篇内容,不讲政策文件,不列长串适配清单,就聚焦一个最实际的问题:一个标准的Spring Boot Web项目(Maven构建、内嵌Tomcat默认配置),如何零修改核心业务代码,仅通过调整构建方式、依赖和少量配置,就能在TongWeb上跑起来,并能进管理后台查应用状态、看日志、启停服务?我会把整个过程拆成四步:先搞懂TongWeb和Tomcat的根本差异在哪;再手把手带你改pom.xml、改启动类、打WAR包;然后教你怎么在Linux服务器上静默安装TongWeb、配置JDK、部署应用;最后给你一份真实压测场景下的调优参数和三个我踩过的坑——比如为什么你的Spring Boot Actuator端点在TongWeb里返回500,为什么MyBatis的SQL日志死活不打印,为什么上传大文件时总卡在80%不动。这些细节,官网文档不会写,但你在现场一定会遇到。
2. 核心设计思路:不是“替换”,而是“解耦+适配”
2.1 Spring Boot的Servlet容器抽象层,才是我们真正的杠杆
很多人以为Spring Boot“内置Tomcat”就意味着和Tomcat强绑定,这是个巨大误解。Spring Boot真正依赖的,从来不是Tomcat本身,而是Servlet规范(3.1+)和Java EE(现Jakarta EE)的Web容器契约。Tomcat、Jetty、Undertow,甚至TongWeb、金蝶Apusic、宝兰德BES,只要它们实现了Servlet 3.1+规范,Spring Boot就能和它们“握手”。这个握手协议,就是spring-boot-starter-web里的ServletWebServerFactory接口。
我们来看一段Spring Boot启动时的真实日志:
2024-06-15 10:23:45.123 INFO 12345 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)这行日志里的关键词是TomcatWebServer,但它只是ServletWebServerFactory的一个实现。如果你把spring-boot-starter-tomcat这个starter从依赖里去掉,Spring Boot启动时就会报错:“No ServletWebServerFactory bean found”。但如果你自己提供一个TongWebServletWebServerFactory(当然,东方通没提供这个,我们也不需要),或者更现实的做法——放弃内嵌容器,走传统WAR部署模式,那整个问题就从“如何替换内嵌容器”降维成“如何让Spring Boot应用符合Servlet容器的部署规范”。
这就是整个方案的设计原点:不硬刚TongWeb的内嵌集成,而是回归Servlet标准,用最成熟、最可控的WAR包方式交付。这样做的好处是三点:第一,完全规避了TongWeb对Spring Boot内嵌容器的兼容性黑盒;第二,部署流程和原有Tomcat环境几乎一致,运维同学不用学新命令;第三,所有Spring Boot特性(自动配置、Actuator、Profile)全部保留,你只是换了个“房子”,没换“家具”。
2.2 TongWeb和Tomcat的三大本质差异,决定了我们必须改什么
光知道要打WAR包还不够。TongWeb不是Tomcat的“国产马甲”,它在底层实现上有三处关键差异,直接影响你的Spring Boot项目能否顺利启动:
第一,类加载器模型不同。Tomcat采用经典的双亲委派模型(Parent-Delegation),应用类优先从WEB-INF/lib加载,找不到才向上委托给Common ClassLoader。而TongWeb V7.0默认采用逆向委派(Child-First)模式,即先从应用自己的WEB-INF/lib找类,找不到才去父加载器(如System ClassLoader)里找。这听起来是好事,但恰恰是很多ClassNotFoundException的根源。比如Spring Boot的spring-boot-autoconfigure里有个DataSourceAutoConfiguration类,它依赖javax.sql.DataSource,这个接口在JDK里,也在TongWeb的lib目录下。如果TongWeb的类加载器先从自己的lib里加载了一个旧版javax.sql(比如JDK 8的),而你的Spring Boot用的是JDK 11的API,就会出现IncompatibleClassChangeError。解决方案?不是改代码,而是在TongWeb的server.xml里显式关闭逆向委派,或者更稳妥地,在WAR包的WEB-INF/web.xml里加一行<loader delegate="true"/>,强制走标准双亲委派。
第二,JNDI资源绑定机制不同。Tomcat里配置数据源,你习惯写context.xml,然后在Spring里用@Resource(name="jdbc/mydb")注入。TongWeb也支持JNDI,但它的JNDI命名空间是java:comp/env/,而且默认不启用全局JNDI。如果你的Spring Boot项目里用了@ConfigurationProperties(prefix="spring.datasource.jndi"),在TongWeb里很可能查不到JNDI Context。实测下来,最稳的方式是放弃JNDI,改用Spring Boot原生的application.yml配置数据源,把连接池(HikariCP)的配置项全写明,这样既脱离容器依赖,又便于多环境切换。
第三,管理控制台的权限体系和URL路径完全不同。Tomcat的管理界面是/manager/html,用户名密码在tomcat-users.xml里配。TongWeb的管理后台默认地址是http://localhost:6888/console,初始账号是admin/tongweb(注意:不是tongweb123,网上很多教程写错了),而且首次登录后强制改密。更重要的是,TongWeb的“应用部署”功能,不叫“Deploy”,叫“应用发布”,它不接受直接上传WAR包,而是要求你先把WAR包放到webapps目录下,再在控制台里点“发布”按钮,或者用命令行工具twctl。这点新手极易卡住——你传了WAR包,刷新页面却看不到应用列表,因为没点“发布”。
这三点差异,就是我们后续所有操作的底层逻辑。记住:不是TongWeb“不兼容”Spring Boot,而是它遵循了一套略有不同的企业级中间件规范。我们的任务,是做适配,不是做对抗。
3. 实操全流程:从Spring Boot项目到TongWeb成功运行
3.1 第一步:改造Spring Boot项目,生成标准WAR包
假设你有一个标准的Spring Boot Web项目,结构如下:
my-springboot-app/ ├── pom.xml ├── src/ │ └── main/ │ ├── java/com/example/app/ │ │ └── MyApplication.java │ └── resources/application.yml └── target/my-springboot-app-1.0.0.jar现在我们要把它变成能在TongWeb里跑的WAR包。核心改动只有三处,全部在pom.xml和启动类里,业务代码一行都不用动。
第一步:修改pom.xml,移除内嵌Tomcat,添加WAR打包支持
找到pom.xml里的spring-boot-starter-web依赖,把它改成:
<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> <!-- 添加Servlet API依赖,这是WAR包必需的 --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <scope>provided</scope> </dependency>然后,在pom.xml的<packaging>标签里,把jar改成war:
<packaging>war</packaging>最后,添加spring-boot-starter-tomcat为provided范围,注意:这里不是为了用它,而是为了让Spring Boot的编译期能识别Servlet API:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <scope>provided</scope> </dependency>提示:
provided范围意味着这个依赖只在编译和测试时有效,打包时不会打进WAR包,避免和TongWeb自带的Servlet实现冲突。这是Maven WAR打包的黄金法则。
第二步:改造启动类,继承SpringBootServletInitializer
打开你的主启动类MyApplication.java,让它继承SpringBootServletInitializer,并重写configure方法:
@SpringBootApplication public class MyApplication extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { // 这里必须return builder.sources(MyApplication.class),不能写错类名 return builder.sources(MyApplication.class); } public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }这个configure方法是关键。它告诉Spring Boot:“我不是独立运行的Jar,我是要被Servlet容器加载的WAR,请按Web应用的方式初始化上下文。”没有这一步,TongWeb启动时会找不到Spring的根上下文,所有@Controller、@Service都会失效。
第三步:验证打包,生成WAR文件
执行Maven命令:
mvn clean package -Dmaven.test.skip=true你会在target/目录下看到my-springboot-app-1.0.0.war。用jar -tf target/my-springboot-app-1.0.0.war | head -20检查一下结构,确认里面有:
WEB-INF/classes/(你的编译字节码)WEB-INF/lib/(所有依赖jar,包括spring-boot-starter-web)WEB-INF/web.xml(Spring Boot自动生成的,不用手动创建)
注意:Spring Boot 2.3+版本默认不生成
web.xml,这是完全OK的。Servlet 3.0+规范支持注解驱动,TongWeb V7.0完全支持。如果你的项目用了老版本Spring Boot(<2.3),建议升级,否则可能需要手写web.xml。
3.2 第二步:在Linux服务器上静默安装TongWeb V7.0
TongWeb的安装包是.bin格式,官网下载后,需要在目标服务器(麒麟V10 + 飞腾CPU)上执行。这里强调“静默安装”,因为生产环境通常不允许图形界面,且要写入自动化脚本。
前提检查:JDK版本与环境变量
TongWeb V7.0官方支持JDK 8u261+ 或 JDK 11。我们以JDK 11为例(推荐,因Spring Boot 2.7.x对JDK 11优化更好):
# 查看当前JDK java -version # 输出应为 openjdk version "11.0.22" 2024-04-16 # 确认JAVA_HOME指向正确路径 echo $JAVA_HOME # 应输出 /usr/lib/jvm/java-11-openjdk-arm64 (飞腾架构需用arm64版本) # 如果未设置,编辑 /etc/profile sudo vi /etc/profile # 在末尾添加: export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-arm64 export PATH=$JAVA_HOME/bin:$PATH source /etc/profile执行静默安装
下载的安装包假设为tongweb-v7.0-linux-aarch64.bin(注意aarch64是飞腾CPU的架构标识):
# 赋予执行权限 chmod +x tongweb-v7.0-linux-aarch64.bin # 创建安装目录 sudo mkdir -p /opt/tongweb # 执行静默安装(-i silent 是关键参数) sudo ./tongweb-v7.0-linux-aarch64.bin -i silent -DINSTALLDIR=/opt/tongweb -DJAVA_HOME=/usr/lib/jvm/java-11-openjdk-arm64安装完成后,TongWeb的主目录是/opt/tongweb,核心目录结构如下:
/opt/tongweb/ ├── bin/ # 启动/停止脚本:start.sh, stop.sh, twctl ├── conf/ # 主配置:server.xml, web.xml ├── lib/ # TongWeb核心jar包 ├── logs/ # 日志文件 ├── webapps/ # 应用部署目录(WAR包放这里) └── console/ # 管理控制台静态资源启动TongWeb并验证
# 切换到bin目录 cd /opt/tongweb/bin # 启动(后台运行) ./start.sh # 查看进程 ps -ef | grep tongweb # 应看到类似:/usr/lib/jvm/java-11-openjdk-arm64/bin/java ... -Dcatalina.home=/opt/tongweb ... # 查看日志确认启动成功 tail -f /opt/tongweb/logs/stdout.log # 等待出现 "Server startup in [xxx] milliseconds" 即成功此时,访问http://你的服务器IP:6888/console,用默认账号admin/tongweb登录。首次登录后,系统会强制你修改密码(新密码需包含大小写字母+数字+特殊字符,8位以上)。改完密码,你就进入了TongWeb的管理后台。
3.3 第三步:部署WAR包,配置应用参数
上传WAR包到webapps目录
用scp或rz命令,把本地生成的my-springboot-app-1.0.0.war上传到服务器:
scp target/my-springboot-app-1.0.0.war user@server-ip:/opt/tongweb/webapps/上传后,不要急着去控制台点“发布”。先检查webapps目录:
ls -l /opt/tongweb/webapps/ # 你会看到 my-springboot-app-1.0.0.war 文件,以及一个同名的空文件夹 my-springboot-app-1.0.0/ # 这是TongWeb自动解压的痕迹,说明它已识别到WAR包在管理控制台发布应用
- 登录
http://server-ip:6888/console。 - 左侧菜单栏,点击“应用管理” → “应用发布”。
- 在右侧列表中,找到
my-springboot-app-1.0.0(注意:显示的是解压后的文件夹名,不是WAR包名)。 - 勾选它,点击上方的“发布”按钮。
- 弹窗确认,点击“确定”。
发布成功后,状态会从“未发布”变成“已发布”,但此时应用还是“停止”状态。你需要再选中它,点击“启动”按钮。
注意:TongWeb的“发布”和“启动”是两个独立操作。只发布不启动,应用不会运行;只启动未发布,会报错“应用未发布”。
配置应用JVM参数(关键调优)
Spring Boot应用在TongWeb里跑,内存不足是常见问题。默认JVM参数太小(-Xms256m -Xmx512m),对于一个带MyBatis和Redis的中型应用远远不够。我们需要修改/opt/tongweb/bin/setenv.sh:
# 编辑setenv.sh vi /opt/tongweb/bin/setenv.sh # 找到JAVA_OPTS行,修改为(根据你的服务器内存调整): JAVA_OPTS="-Xms2g -Xmx4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"然后重启TongWeb:
./stop.sh && ./start.sh验证应用是否正常运行
启动后,查看/opt/tongweb/logs/stdout.log,搜索my-springboot-app,应看到类似日志:
2024-06-15 14:30:22.123 INFO 12345 --- [main] com.example.app.MyApplication : Started MyApplication in 25.432 seconds (JVM running for 28.765)再访问应用URL:http://server-ip:6888/my-springboot-app-1.0.0/(注意:上下文路径默认是WAR包名,不含.war后缀)。如果看到你的首页,恭喜,第一步成功!
3.4 第四步:打通Actuator、日志、文件上传等关键能力
Spring Boot的便利性,很大一部分来自Actuator健康检查、Logback日志、@RequestBody文件上传等。这些在TongWeb里不是开箱即用,需要针对性配置。
启用并访问Actuator端点
在application.yml里确保开启了Actuator:
management: endpoints: web: exposure: include: "*" # 或者明确列出 health,info,metrics,loggers endpoint: health: show-details: always但直接访问http://server-ip:6888/my-springboot-app-1.0.0/actuator/health可能返回404或500。原因有两个:一是TongWeb的Servlet映射规则,二是Actuator的WebMvcEndpointHandlerMapping需要额外配置。
解决方案:在application.yml里加一行:
spring: web: resources: static-locations: classpath:/static/,classpath:/public/ mvc: static-path-pattern: /static/** # 关键:指定Actuator的base-path management: endpoints: web: base-path: /actuator这样,Actuator端点就固定在/actuator/health,而不是被TongWeb的默认Servlet拦截。
让MyBatis SQL日志在TongWeb里正常打印
很多开发者发现,logging.level.org.mybatis=DEBUG在TongWeb里不生效。这是因为TongWeb自身的日志框架(Log4j2)和Spring Boot的Logback存在冲突。解决方法是统一用Logback,并禁用TongWeb的Log4j2。
在src/main/resources/下,创建logback-spring.xml:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <include resource="org/springframework/boot/logging/logback/defaults.xml"/> <property name="LOG_FILE" value="${LOG_FILE:-${LOG_PATH:-${LOG_TEMP:-${java.io.tmpdir:-/tmp}}/logs}/spring.log}"/> <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>${CONSOLE_LOG_PATTERN}</pattern> </encoder> </appender> <!-- 关键:为MyBatis开启DEBUG --> <logger name="org.mybatis" level="DEBUG" additivity="false"> <appender-ref ref="CONSOLE"/> </logger> <root level="INFO"> <appender-ref ref="CONSOLE"/> </root> </configuration>然后,在pom.xml里排除TongWeb自带的Log4j2:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- 显式引入Logback --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-log4j2</artifactId> <exclusions> <exclusion> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </exclusion> </exclusions> </dependency>解决大文件上传413错误
Spring Boot默认单文件上传限制是1MB,TongWeb默认也是1MB。当用户上传大于1MB的文件时,会返回HTTP 413。需要两边同时调大:
在application.yml里:
spring: servlet: multipart: max-file-size: 50MB max-request-size: 50MB在TongWeb的/opt/tongweb/conf/server.xml里,找到<Connector>标签,添加maxPostSize属性:
<Connector port="6888" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxPostSize="52428800" /> <!-- 50MB = 52428800 bytes -->重启TongWeb,问题解决。
4. 常见问题排查与独家避坑指南
4.1 问题速查表:从404到500,一线排障记录
| 现象 | 可能原因 | 排查步骤 | 解决方案 |
|---|---|---|---|
| 首页404,但TongWeb日志无报错 | WAR包未发布,或上下文路径错误 | 1. 登录控制台,确认应用状态为“已启动” 2. ls /opt/tongweb/webapps/,确认解压后的文件夹存在3. 访问URL是否带了 .war后缀(错误:/app.war/;正确:/app/) | 在控制台“应用发布”里发布并启动应用;URL去掉.war |
启动时报ClassNotFoundException: org.springframework.web.context.ContextLoaderListener | spring-boot-starter-web依赖未排除Tomcat,或javax.servlet-api范围不是provided | 1.jar -tf target/app.war | grep servlet-api,确认javax.servlet-api没被打包进去2. 检查 pom.xml,确认spring-boot-starter-tomcat被exclusion且scope=provided | 严格按3.1节修改pom.xml,重新打包 |
Actuator/actuator/health返回500,日志报NoSuchBeanDefinitionException | WebMvcEndpointHandlerMapping未正确注册 | 1.grep -r "WebMvcEndpointHandlerMapping" /opt/tongweb/logs/2. 检查 application.yml是否漏了management.endpoints.web.base-path | 在application.yml中显式设置management.endpoints.web.base-path: /actuator |
MyBatis SQL不打印,logging.level.org.mybatis=DEBUG无效 | TongWeb的Log4j2与Logback冲突 | 1.ps aux | grep java,确认JVM参数里没有-Dlog4j.configurationFile2. cat /opt/tongweb/conf/log4j2.xml,确认是否存在 | 删除/opt/tongweb/conf/log4j2.xml,或重命名为log4j2.xml.bak,强制使用Logback |
| 上传文件时进度条卡在80%,最终超时 | TongWeb的maxPostSize和Spring Boot的maxRequestSize不一致 | 1.curl -X POST http://ip:6888/app/upload -F "file=@large.zip",观察响应时间2. tail -f /opt/tongweb/logs/stdout.log,搜索timeout | 同时修改application.yml和server.xml中的大小限制,保持一致 |
4.2 三个血泪教训:那些官网不会写的细节
教训一:TongWeb的“热部署”是把双刃剑
TongWeb控制台有个“热部署”开关,默认是开启的。意思是,当你修改了webapps/app/WEB-INF/classes/下的class文件,TongWeb会自动重新加载。这听起来很爽,但对Spring Boot是灾难。因为Spring Boot的ApplicationContext是单例且复杂的,热加载会导致Bean定义冲突、AOP代理失效、甚至内存泄漏。我亲眼见过一个项目,热部署三次后,/actuator/metrics返回的JVM内存指标翻了五倍,最后OOM。
我的做法:在
/opt/tongweb/conf/server.xml里,把<Context>标签的reloadable属性设为false:<Context path="/myapp" docBase="my-springboot-app-1.0.0" reloadable="false" />生产环境,永远用“停-替-启”三步法更新代码,别信热部署。
教训二:application.yml里的spring.profiles.active在TongWeb里可能失效
Spring Boot默认从spring.profiles.active读取激活的Profile,但在TongWeb里,这个值有时会被容器的系统属性覆盖。比如TongWeb启动脚本里设置了-Dspring.profiles.active=prod,就会覆盖你application.yml里的配置。
我的做法:不依赖
application.yml,改用JVM参数指定:# 修改 /opt/tongweb/bin/start.sh,在JAVA_OPTS里加 JAVA_OPTS="$JAVA_OPTS -Dspring.profiles.active=prod"这样,Profile的优先级最高,且所有环境一致。
教训三:TongWeb的/console管理后台,千万别用Chrome最新版直连
这不是Bug,是兼容性问题。TongWeb V7.0的控制台前端用的是较老的ExtJS框架,而Chrome 120+默认禁用了某些旧版JavaScript API(如document.all)。结果就是,登录后页面白屏,F12看Console全是ReferenceError。
我的做法:给运维同学发一条命令,让他们在Chrome里输入
chrome://flags/#unsafely-treat-insecure-origin-as-secure,把Insecure origins treated as secure设为Enabled,然后重启Chrome。或者,更简单——用Firefox ESR(企业版)或Edge的IE模式访问http://ip:6888/console,100%兼容。
5. 后续可扩展方向:不止于“能跑”,更要“跑好”
做到上面四步,你的Spring Boot应用已经在TongWeb上稳定运行了。但这只是信创迁移的第一公里。接下来,你可以沿着这三个方向深挖,把项目做得更扎实:
第一,集成TongWeb的集群能力。TongWeb支持多节点集群,通过conf/tongweb-cluster.xml配置。你可以把Spring Boot的Session存储从内存改成Redis,再配合TongWeb的集群广播,实现真正的高可用。这比单纯堆机器更有技术含量。
第二,对接东方通的统一认证中心(UAC)。很多政企项目要求单点登录(SSO)。TongWeb提供了标准的CAS和OAuth2.0接入方式。你不需要重写登录逻辑,只需在Spring Security里配置CasAuthenticationFilter或OAuth2LoginAuthenticationFilter,把认证委托给UAC。
第三,利用TongWeb的APM监控探针。TongWeb自带性能监控模块,可以采集JVM、SQL、HTTP请求的详细指标。你可以在conf/tongweb-monitor.xml里开启探针,然后把数据推送到Prometheus,用Grafana画出你的Spring Boot应用的实时性能大盘。这才是信创项目该有的技术水位。
最后分享一个小技巧:每次升级TongWeb小版本(比如从V7.0.1到V7.0.2),别急着全量上线。先用twctl命令行工具,在测试环境部署一个最小化的Spring Boot Hello World WAR包,跑通/actuator/health和/actuator/metrics,确认基础能力没问题,再上业务应用。信创落地,稳字当头,快不如准。