Spring Cloud Gateway 打 War 包部署外置 Tomcat 全攻略:原理、实现与踩坑实录

发布时间:2026/6/27 7:16:56
Spring Cloud Gateway 打 War 包部署外置 Tomcat 全攻略:原理、实现与踩坑实录 这是一篇深度技术复盘记录了一套让标准 Spring Cloud GatewayWebFlux 版以 War 包形式部署到外置 Tomcat 的完整改造方案包含核心原理剖析、源码级实现和实际效果评估。一、背景与动机Spring Cloud GatewaySCG作为微服务网关的标配官方设计上以jar 包 内嵌 Netty运行不支持 War 包外置 Servlet 容器部署。但在一些传统企业环境中运维基础设施强依赖外置 Tomcat 统一托管多个 War 应用这催生了能否让 SCG 也能打成 War 包跑在 Tomcat 里的需求。本文完整记录了这条探索之路从底层请求链路的分析到核心改造方案再到最终的效果评估。二、请求处理全链路剖析在动手改造前先理清 SCG 在 Servlet 容器中的请求流转全过程。2.1 请求流程调用链DefaultWebFilterChain#filter → DispatcherHandler#handle → RouterFunctionMapping#getHandlerInternal → RoutePredicateHandlerMapping#getHandlerInternal → RoutePredicateHandlerMapping#lookupRoute → this.routeLocator.getRoutes() → concatMap(route → Mono.just(route).filterWhen(r → { exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId()); return r.getPredicate().apply(exchange); })) → CachingRouteLocator#getRoutes 【关键卡点获取路由信息】 → DispatcherHandler#invokeHandler → SimpleHandlerAdapter#handle → FilteringWebHandler#handle → DefaultGatewayFilterChain#filter → NettyRoutingFilter#filter其中CachingRouteLocator#getRoutes是整个流程的命门——如果前面的getRoutes()没有正确onNext后续路由匹配将直接卡死请求无法到达后端服务。2.2 核心类职责一览类角色职责SpringServletContainerInitializer容器入口扫描并执行所有WebApplicationInitializerAbstractReactiveWebInitializer官方适配Spring-web 提供的 Reactive→Servlet 适配实现WebHttpHandlerBuilderHttpHandler 构造组装HttpHandler实现类为HttpWebHandlerAdapterServletHttpHandlerAdapterServlet 实现将ServletRequest/ServletResponse适配为ServerHttpRequest/ServerHttpResponseHttpWebHandlerAdapterHttpHandler网络层→应用层转换封装ServerWebExchangeDispatcherHandlerWebHandler核心调度器遍历所有HandlerMapping并执行匹配HandlerMapping路由匹配包含RoutePredicateHandlerMappingGateway 路由和RouterFunctionMapping2.3 请求处理流程简图ServletHttpHandlerAdapterServlet ↓ 适配 Servlet 请求 HttpWebHandlerAdapterHttpHandler ↓ 封装 ServerWebExchange DispatcherHandlerWebHandler ↓ 遍历匹配 HandlerMapping ↓ 路由到目标服务三、改造方案详解3.1 官方适配方案的问题Spring-web 包中提供了AbstractReactiveWebInitializer但在实际运行中发现通过它适配 Servlet 时缺少大量 WebFlux 运行时必需的 Bean导致路由无法匹配、过滤器失效等问题。根本原因在于ApplicationContext的创建方式与 jar 包模式不一致。3.2 自定义 WebApplicationInitializer核心思路参考AbstractReactiveWebInitializer的流程但改用SpringApplication.run()启动上下文保持与 jar 包启动方式完全一致。3.2.1 ApplicationContext 的创建使用new SpringApplication(...).run()创建 ApplicationContext与 jar 启动方式一致能避免运行时配置相关的各类问题。但问题来了SpringApplication.run()会同时启动内嵌 WebServerNetty/Tomcat而 War 包运行在外置 Servlet 容器中不需要额外的 WebServer。解决方案实现一个空壳WebServer 排除自动配置类。3.2.2 处理 WebServer 冲突排除ReactiveWebServerFactoryAutoConfiguration自动配置类创建一个空实现的ReactiveWebServerFactory和WebServer仅在 Servlet 容器运行时生效jar 包运行时完全不影响3.2.3 HttpWebHandlerAdapter 的获取通过SpringApplication.run()创建上下文后WebFlux 相关自动配置类正常生效HttpWebHandlerAdapter已作为 Bean 注册直接从 ApplicationContext 获取即可无需再通过WebHttpHandlerBuilder创建。3.3 核心实现代码abstractclassAbstractReactiveWebApplicationInitializerimplementsWebApplicationInitializer{publicstaticfinalStringIS_EMBEDDEDReactiveWebApplicationInitializer.isEmbedded;// 空实现的 WebServerpublicstaticfinalWebServerNOOP_WEB_SERVERnewWebServer(){Overridepublicvoidstart()throwsWebServerException{}Overridepublicvoidstop()throwsWebServerException{}OverridepublicintgetPort(){return-1;}};publicstaticfinalReactiveWebServerFactoryNOOP_REACTIVE_WEB_SERVER_FACTORYnewReactiveWebServerFactory(){OverridepublicWebServergetWebServer(HttpHandlerhttpHandler){returnNOOP_WEB_SERVER;}};// 仅在 Servlet 容器中生效的配置ConfigurationConditionalOnProperty(valueIS_EMBEDDED,havingValuefalse)publicstaticclassNoEmbeddedAutoConfiguration{// 排除内嵌 WebServer 自动配置ConfigurationEnableAutoConfiguration(exclude{ReactiveWebServerFactoryAutoConfiguration.class})publicclassDisableReactiveWebServerFactoryAutoConfiguration{}// 注册空 WebServer FactoryBeanpublicReactiveWebServerFactorynoopReactiveWebServerFactory(){returnNOOP_REACTIVE_WEB_SERVER_FACTORY;}}OverridepublicvoidonStartup(ServletContextservletContext)throwsServletException{// 创建 ApplicationContextApplicationContextapplicationContextcreateApplicationContext(servletContext);// 获取 HttpHandlerHttpHandlerhttpHandlerapplicationContext.getBean(HttpHandler.class);// 注册 ServletHttpHandlerAdapter...}protectedApplicationContextcreateApplicationContext(ServletContextservletContext){returncreateSpringApplication(servletContext).run();}protectedSpringApplicationcreateSpringApplication(ServletContextservletContext){SpringApplicationspringApplicationnewSpringApplication(getConfigClasses());MapString,ObjectdefaultPropertiesnewHashMap(16);// 标记为非内嵌模式defaultProperties.put(IS_EMBEDDED,false);// 设置 context-pathdefaultProperties.put(server.servlet.context-path,servletContext.getContextPath());springApplication.setDefaultProperties(defaultProperties);returnspringApplication;}protectedClass?[]getConfigClasses(){returnnewClass[]{getClass()};}}3.4 处理 Context-Path 不兼容问题War 包部署必然带context-path如/gateway而 Spring Cloud Gateway 和低版本 RouterFunction 的 Path 条件不兼容context-path会导致请求 404。解决方法AOP 切面修复。使用 AOP 拦截DispatcherHandler#handle(ServerWebExchange)方法在执行前修改ServerWebExchange中ServerHttpRequest的 Path将context-path前缀去掉再替换原ServerWebExchange继续后续路由匹配。四、效果评估与局限性分析4.1 可正常工作的能力能力状态基础路由转发、断言匹配✅ 正常普通 GlobalFilter、局部 Filter✅ 正常Nacos/Apollo 配置路由、动态路由刷新✅ 正常普通 HTTP 接口、健康检查端点✅ 正常外置 Tomcat 多 War 包共存context-path 隔离✅ 正常4.2 存在的致命短板1底层 IO 模型冲突响应式核心能力受损标准 Gateway 核心优势是Netty 非阻塞 Reactor 响应式模型而 War 部署外层套了Tomcat Servlet 阻塞容器外层 Tomcat 是 BIO/NIO 阻塞线程池内层 Gateway 是 Reactor 非阻塞两层嵌套导致线程池互相阻塞、吞吐量暴跌背压机制失效高并发下极易出现请求堆积、超时WebSocket、SSE 长连接稳定性差大量场景断连、消息丢失2Gateway 高阶特性兼容 BugNettyRoutingFilter底层依赖 Netty 客户端外层 Tomcat 导致长链接复用、连接池管控异常限流组件RequestRateLimiter基于 Redis Reactor在 Servlet 适配层出现限流计数不准全局 CORS、路径重写、Header 转换过滤器偶现失效灰度路由、权重路由、负载均衡在高并发下匹配异常3生命周期与监控缺陷空 WebServer 只是空壳健康探针错乱Actuator 部分 metrics 指标丢失Netty 连接数、响应式线程指标Tomcat 关闭时Reactor 上下文、Netty 客户端连接无法优雅销毁出现句柄泄漏动态路由热更新偶发失效CachingRouteLocator缓存刷新线程与 Tomcat 线程竞争锁4Context-Path 修复是临时补丁通过 AOP 修改ServerHttpRequest路径属于侵入式改造多层路径、正则路由、重写路径场景下路径替换逻辑易出错链路追踪Sleuth/Micrometertrace 路径统计错乱日志路径与实际访问路径不一致5官方不支持无版本兼容保障Spring 官方明确WebFlux Gateway 不支持 War 部署此方案为企业自研 HackSpring Cloud / Spring Boot 版本升级后DispatcherHandler、ServletHttpHandlerAdapter内部类结构变更改造代码大概率失效出现问题无法在开源社区提 Issue只能自行排坑4.3 部署层面隐患Tomcat 线程池资源隔离差网关流量打满会导致同 Tomcat 下其他 War 应用卡死无法利用 Netty 专属优化连接复用、零拷贝、EPollQPS 相比原生 jar 包下降 50% 以上容器化K8s/Docker部署不友好行业标准统一使用 jar 包War 外置 Tomcat 运维链路更复杂五、场景适用性判定✅ 勉强可用的场景内部低并发管理网关仅少量路由、无长连接需求、测试/预发布环境基础转发、简单过滤器能正常工作可作为临时方案使用。❌ 严禁上线的场景线上流量入口网关高并发、WebSocket、限流灰度、生产核心流量存在性能、稳定性、可观测性多重隐患严禁上线。六、总结与建议从不同维度看这套方案维度评价能否打包 War、能否启动✅ 完整可行基础路由是否生效✅ 基本正常Gateway 完整功能❌ 大量高阶特性不兼容生产稳定性❌ 存在底层架构冲突性能❌ QPS 下降 50% 以上官方规范❌ 官方不支持属 Hack 方案终极建议最优方案放弃 War 部署幻想使用原生 jar 包 内嵌 Netty这是 Spring Cloud Gateway 的设计初衷也是唯一官方保证稳定的运行方式。折中方案如果运维环境强制要求 Servlet 容器改用spring-cloud-starter-gateway-mvcSpring Cloud Gateway 官方 Servlet 版。虽然并发能力不如 WebFlux 版但无需任何侵入式 Hack 改造稳定性和可维护性有保障。本文方案定位这是一次技术探索和验证证明了理论可行但生产不推荐适合作为学习 Spring Cloud Gateway 内部机制和 WebFlux-Servlet 适配原理的参考材料。后记技术选型时能跑起来和能稳定跑在生产环境之间有巨大的鸿沟。在底层架构模型冲突面前任何 Hack 补丁都是脆弱的。尊重框架的设计哲学往往是最省力的路。