WebLogic到Undertow:容器迁移中的兼容性问题

1. 对应简历段落

这篇文章对应简历中“将部署在 WebLogic 的 Spring3 + MyBatis 遗留应用迁移到 Spring Boot + Undertow 内嵌容器”的项目经历。简历可以写成:

负责核心保险业务系统从 WebLogic WAR 部署向 Spring Boot + Undertow 可执行 JAR 部署迁移,完成 JNDI 数据源替换、类加载冲突治理、Servlet 过滤器顺序适配、Session 与编码兼容、连接池和线程池参数重建,并通过灰度切流、日志回放和压测保障迁移期间交易链路稳定。

面试官看到这段,通常不会满足于“换了一个容器”。真正的追问会集中在:WebLogic 和 Undertow 的运行模型有什么差异?原来依赖容器提供的能力迁移后由谁承担?为什么上线后可能出现 NoSuchMethodError、乱码、上传失败、事务行为变化、连接池耗尽?如果生产上出现接口偶发 500,你如何判断是容器兼容问题还是业务代码问题?

回答这类问题时,要把容器迁移讲成一次“运行时契约重建”。WebLogic 是完整 Java EE 应用服务器,承担了连接池、JNDI、安全域、部署描述符、类加载隔离、线程池、Session 管理等职责。Undertow 是轻量级 Servlet 容器,优势是启动快、内嵌部署简单、适合 Spring Boot 服务化,但它不会自动继承 WebLogic 的历史配置。迁移的难点不是启动一个 Boot 应用,而是把过去藏在服务器控制台、部署描述符和运维脚本里的隐性配置全部显式化、版本化、可验证。

2. 业务背景

保险公司很多核心系统早期采用 WebLogic 部署,原因很现实:当年 Java EE 体系成熟,WebLogic 提供控制台、集群、JNDI、连接池、事务、安全域、监控和热部署,适合支撑大型企业应用。一个传统系统可能以 WAR 包部署,内部包含销售出单、保费试算、客户查询、保单状态同步、回访工单、运营报表等模块。数据库连接池在 WebLogic 控制台配置,应用通过 JNDI 名称获取;上下文路径、Session 超时、错误页和资源引用写在 web.xmlweblogic.xml 或运维文档中;部分公共 jar 放在 domain lib 或 server lib 下。

随着业务发展,这种模式的成本越来越高。第一,部署周期长。一个大 WAR 包启动慢,发布窗口长,失败后回滚也重。第二,环境差异难治理。开发、测试、预发、生产的 WebLogic 控制台配置不一定一致,很多参数靠人工维护。第三,弹性不足。传统应用服务器扩缩容成本高,难以适配容器化或轻量化运维。第四,可观测性弱。日志散在多台机器,线程池、连接池、请求链路很难与业务 traceId 关联。第五,技术升级受阻。Spring Boot、Nacos、Gateway、ELK、XXL-Job 等体系更适合内嵌容器和标准化启动参数。

容器迁移的业务目标不是“把 WebLogic 干掉”,而是降低发布和运维复杂度,使系统具备服务化、灰度、快速回滚和统一监控能力。保险业务又要求高连续性,尤其是投保、支付、核保、批改、理赔报案等链路不能因为容器差异产生业务异常。因此迁移必须以兼容性清单为中心推进。

3. 核心原理

WebLogic 和 Undertow 的最大差异在于角色定位。WebLogic 是应用服务器,它不仅实现 Servlet 规范,还提供完整企业容器能力。Undertow 是高性能 Web 服务器和 Servlet 容器,Spring Boot 将其作为内嵌运行时时,应用自己负责绝大部分企业能力集成。

第一是类加载模型。WebLogic 有多层 ClassLoader,可能先加载服务器级 jar,也可以通过 prefer-application-packages 让应用优先。很多老项目在 WebLogic 上运行多年,并不代表依赖没有冲突,而是冲突被容器加载顺序掩盖了。迁移到 Spring Boot 可执行 JAR 后,依赖由 Boot loader 和应用 classpath 决定,原来容器提供的类不再存在,原来被服务器版本覆盖的 jar 也会暴露。典型错误包括 ClassNotFoundExceptionNoSuchMethodErrorNoClassDefFoundError 和日志框架绑定冲突。

第二是数据源和事务。WebLogic 数据源通常通过 JNDI 暴露,连接池参数由控制台维护。迁移到 Boot 后,多数项目改为 HikariCP、Druid 或公司统一数据源组件。这个变化会影响最大连接数、空闲连接、连接校验、超时、自动提交、事务隔离级别、泄漏检测、失败重连等行为。事务层面,如果原来依赖 WebLogic JTA,而迁移后只使用本地 DataSourceTransactionManager,跨库或跨资源事务语义会改变。

第三是 Servlet 行为。web.xml 中的 Filter、Listener、Servlet、错误页、欢迎页、编码过滤器、上传配置都要迁移到 Boot 配置或 Java Bean。Undertow 在请求头大小、URL 解码、cookie 处理、multipart 限制、异步请求、HTTP/2、压缩等方面有自己的默认值。老系统如果依赖 WebLogic 的默认编码或上传限制,迁移后容易出现中文乱码、附件上传失败、Header 太大被拒绝、Cookie 属性不一致等问题。

第四是线程模型。WebLogic 使用自己的执行线程池,Undertow 有 IO 线程和 worker 线程。IO 线程负责网络事件,worker 线程处理阻塞业务逻辑。保险系统常见同步调用下游接口、数据库查询、文件生成和报表导出,如果 worker 线程配置过小或被长耗时任务占满,吞吐会下降。容器迁移时必须结合外部接口线程池、数据库连接池和业务超时一起压测。

第五是部署与运维模型。WebLogic 的发布对象是 domain、managed server、WAR、控制台配置;Boot + Undertow 的发布对象是进程、JAR、启动参数、配置中心、注册中心和健康检查。原来在控制台点出来的能力,现在应该进入 Git、Nacos、部署脚本、镜像或 Helm 配置,确保可追溯。

4. 项目落地

项目落地第一步是盘点 WebLogic 资产。不要只看代码仓库,还要看服务器控制台、启动脚本、domain lib、应用部署描述符、JNDI 配置、JMS 配置、证书目录、文件上传目录、日志目录、集群和 Session 配置。很多兼容问题都来自“代码里没有、运行时存在”的配置。可以把资产清单分为:容器级依赖、应用级配置、外部资源、运行参数、可观测配置和发布回滚配置。

第二步是建立可执行 JAR 启动骨架。对于 Spring3 迁移项目,可以先通过 Boot 主类兼容旧 Spring XML,再逐步迁移 web.xml 里的组件。Filter 需要明确顺序,例如 traceId、编码、鉴权、XSS、登录态、日志记录通常有依赖关系。Listener 要确认是否依赖 ServletContext 初始化属性。Servlet 如果是老式报表、文件下载或验证码组件,要确认路径映射和编码。

第三步是替换 JNDI 数据源。做法不是简单把 URL、用户名、密码搬到 application.yml。需要把 WebLogic 控制台中的连接池参数逐项对齐到新连接池:初始连接、最大连接、连接等待超时、连接检测 SQL、失败重试、事务隔离级别、是否自动提交、连接泄漏监控。上线前要做数据库连接数压测,避免新连接池默认最大连接数过小或过大。

第四步是治理类加载冲突。先通过 mvn dependency:tree 固化依赖树,再和旧 WAR 的 WEB-INF/lib、WebLogic domain lib 做差异对比。重点关注 servlet-apispring-*mybatismybatis-springjacksondom4jasmcglibcommons-loggingslf4jlog4j、数据库驱动。对于厂商包和历史补丁包,要进入私服并标注来源。

第五步是做灰度切流。查询类接口先迁,低风险运营功能先迁,核心交易链路最后迁。通过 Gateway 或负载均衡控制少量流量进入新 Undertow 应用,同时保留 WebLogic 旧系统。日志中必须带 traceId、实例名和版本号,方便出现问题时定位流量进入了哪套运行时。

5. 关键代码或流程

数据源替换的核心是把 JNDI 隐式依赖变成显式配置:

@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties(prefix = "app.datasource.core")
    public HikariDataSource coreDataSource() {
        return new HikariDataSource();
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource coreDataSource) {
        return new DataSourceTransactionManager(coreDataSource);
    }
}

配置要能映射旧 WebLogic 连接池参数:

app:
  datasource:
    core:
      jdbc-url: jdbc:oracle:thin:@//db-host:1521/service
      username: ins_core
      password: ${DB_PASSWORD}
      maximum-pool-size: 80
      minimum-idle: 10
      connection-timeout: 30000
      validation-timeout: 3000
      idle-timeout: 600000
      max-lifetime: 1800000
      connection-test-query: SELECT 1 FROM DUAL

Filter 顺序迁移可以显式注册:

@Bean
public FilterRegistrationBean<TraceFilter> traceFilterRegistration(TraceFilter filter) {
    FilterRegistrationBean<TraceFilter> bean = new FilterRegistrationBean<>();
    bean.setFilter(filter);
    bean.addUrlPatterns("/*");
    bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return bean;
}

@Bean
public FilterRegistrationBean<CharacterEncodingFilter> encodingFilterRegistration() {
    CharacterEncodingFilter filter = new CharacterEncodingFilter("UTF-8", true);
    FilterRegistrationBean<CharacterEncodingFilter> bean = new FilterRegistrationBean<>(filter);
    bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
    return bean;
}

迁移流程建议按七步执行:资产盘点、依赖树收敛、本地可启动、测试环境接口回放、性能压测、灰度切流、全量替换。每一步都要有退出条件,例如“所有 Mapper 加载成功”“核心接口响应字段一致”“压测 TP99 不劣于旧系统”“灰度期间错误率无异常”。

6. 常见坑

第一个坑是忽略 WebLogic 控制台配置。很多团队只迁代码,忘记连接池、编码、上传限制、线程池和启动参数,结果新应用能启动但生产行为不一致。

第二个坑是把容器迁移和业务重构混在一起。容器迁移阶段应尽量保持业务逻辑不变,否则出现问题时很难判断是运行时差异还是业务改造引入的缺陷。

第三个坑是连接池参数照抄默认值。HikariCP 默认值适合通用场景,不一定适合保险高峰交易。最大连接数过小会排队,过大可能压垮数据库。

第四个坑是忽视 Filter 顺序。编码、鉴权、日志、数据权限、XSS 过滤器顺序一变,可能导致 traceId 丢失、登录态读取失败或请求体被提前消费。

第五个坑是没有日志版本标识。灰度期间如果日志中没有应用版本、实例和容器类型,排查问题会非常痛苦。

第六个坑是 Session 策略变化。老系统可能依赖 WebLogic 集群 Session 复制,新系统如果变成无状态或 Redis Session,要确认登录态、验证码、购物车式暂存数据是否兼容。

7. 面试追问

  1. WebLogic 和 Undertow 在定位上有什么不同?
  2. 为什么 WebLogic 上能运行的应用,迁移到 Boot 后可能出现类冲突?
  3. JNDI 数据源迁移时需要对齐哪些参数?
  4. Undertow 的 IO 线程和 worker 线程分别负责什么?
  5. 迁移 web.xml 时如何保证 Filter 顺序不变?
  6. 中文乱码一般从哪些层面排查?
  7. 文件上传迁移后失败,可能和哪些容器参数有关?
  8. 如果原系统使用 JTA,迁移后如何保证事务语义?
  9. 灰度切流时如何区分新旧容器产生的错误?
  10. 为什么容器迁移前要做生产日志回放?

8. 推荐回答

如果问“WebLogic 到 Undertow 最大的风险是什么”,可以回答:

最大风险是隐性运行时契约丢失。WebLogic 不只是 Servlet 容器,它还提供 JNDI、连接池、类加载规则、线程池、Session、安全域和部署描述符。迁移到 Undertow 后,这些能力要由 Spring Boot 配置、依赖管理和运维体系显式承接。我们当时先盘点控制台配置和 domain lib,再做依赖树收敛、JNDI 数据源替换、Filter 顺序迁移、编码和上传限制适配,最后通过接口回放和灰度切流验证。

如果问“类加载冲突怎么排查”,可以回答:

我会先对比旧 WAR 的 WEB-INF/lib、WebLogic 服务器 lib 和新 Maven 依赖树,重点看 Spring、MyBatis、Jackson、ASM、CGLIB、日志框架和数据库驱动。运行时出现 NoSuchMethodError 通常说明编译期和运行期类版本不一致,ClassNotFoundException 说明原来依赖容器提供的类没有进入应用依赖。处理方式是版本收敛、排除传递依赖、厂商包入私服,并在测试环境固化依赖基线。

如果问“如何保证迁移后连接池不出问题”,可以回答:

不会直接使用默认值,而是把 WebLogic 控制台里的最大连接、初始连接、等待超时、校验 SQL、失败重试和事务隔离级别逐项映射到新连接池。然后用压测观察数据库连接数、等待线程、慢 SQL、超时和 TP99。保险系统高峰时连接池参数和下游超时是联动的,所以还要检查业务线程池,避免请求线程被外部接口长时间阻塞。

9. 延伸学习路线

第一阶段学习 Servlet 规范和 Spring Boot Web 自动配置,理解 Filter、Listener、Servlet、错误页、multipart、编码、Session 和内嵌容器注册方式。

第二阶段学习 Undertow 线程模型和核心参数,重点看 IO 线程、worker 线程、buffer、最大请求头、请求体限制、连接超时和访问日志。

第三阶段学习 WebLogic 运行模型,包括 domain、managed server、JNDI、数据源、JTA、ClassLoader、部署描述符和控制台配置。

第四阶段学习依赖治理,掌握 Maven 依赖树、BOM、scope、exclusion、私服和冲突定位。

第五阶段学习迁移方法论,把资产盘点、兼容性矩阵、接口回放、压测、灰度、回滚、可观测建设串成完整闭环。面试中能把“容器差异”讲到“业务连续性和工程验证”,才真正体现资深经验。