Spring MVC 接口匹配性能优化:.do后缀引发的性能瓶颈分析

这个分析过程是我发在群里的,然后再让AI整理成文章的(有AI之后,写文章都变懒了😂),请勿介意

在一次线上系统性能排查中(其他项目组的),发现一个看似平常但影响深远的问题------**前端请求接口时携带了 .do 后缀(如 /fileType/findFileList.do),而后端 Spring MVC 的 @RequestMapping 中并未显式包含 .do,导致请求处理过程中触发了大量不必要的路径匹配逻辑,最终造成接口响应延迟高达 78 毫秒。

项目是一个老的技术栈,Tomcat7和SpringMVC项目,就是JSP那一套,所以Spring版本也不是很高


一、背景介绍

当前系统是一个基于 Spring MVC 的 Web 应用,使用 Tomcat 作为容器。前端通过 Ajax 请求后端接口,所有请求均以 .do 结尾,例如:

text 复制代码
https://127.0.0.1/project/fileType/findFileList.do

而在后端控制器中,对应的映射路径为:

java 复制代码
@RequestMapping("/fileType/findFileList")
public ResponseEntity<?> findFileList(...) { ... }

即:前端带 .do,后端不带 .do。

系统共有约 9500个 URL 映射 ,全部注册在 AbstractHandlerMethodMapping.MappingRegistry#urlLookup 中,这是一个 Map<String, List<HandlerMethod>>,用于快速查找匹配的处理器方法。


二、问题复现与定位

1. 线程栈分析

我们在生产环境捕获到多个线程阻塞在以下调用链中:

java 复制代码
org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod
→ addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request)
→ for (T mapping : mappings) { getMatchingMapping(mapping, request); }

关键点在于:

  • lookupPath 是 /fileType/findFileList.do
  • mappingRegistry.getMappingsByUrl(lookupPath) 返回 null(因为注册的是不带 .do 的路径)
  • 进入 addMatchingMappings() 方法,遍历整个 keySet()(好几千个,不是urlLookup中的数量),逐个进行模式匹配

2. 性能对比实测

请求方式 响应时间 是否命中 urlLookup
/fileType/findFileList.do 78ms ❌ 否
/fileType/findFileList 1ms ✅ 是

💡 结论 :当请求路径带有 .do 且未在 @RequestMapping 中声明时,Spring MVC 无法直接命中 urlLookup,只能通过全量遍历 + 模式匹配的方式寻找匹配项,导致性能急剧下降。


三、Spring MVC 路径匹配机制详解

1. 正常流程(直接命中)

当lookupPath为/fileType/findFileList的时候

java 复制代码
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
    addMatchingMappings(directPathMatches, matches, request);
}
  • 若 lookupPath 在 urlLookup 中存在,则直接返回对应 Handler,无需遍历。

2. 回退机制(全量扫描)

当lookupPath为/fileType/findFileList.do的时候getMappingsByUrl() 返回 null 时,执行:

java 复制代码
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
  • 遍历所有已注册的路径

3. 匹配逻辑细节

java 复制代码
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
    for (T mapping : mappings) {
        if (match = getMatchingMapping(mapping, request)) {
            matches.add(new Match(match, this.mappingRegistry.getMapping(mapping)));
        }
    }
}

其中 getMatchingMapping() 内部会调用 AntPathMatcher.match(),涉及正则表达式匹配、通配符解析等操作,每次调用都消耗一定 CPU 资源。


四、为何 .do 能"自动"匹配?

尽管后端没有写 .do,但前端请求仍能成功访问,是因为:

Spring MVC 默认开启后缀匹配(suffix pattern matching)

具体体现在 RequestConditionHolder 和 PatternsRequestCondition 的实现中,当启用 useSuffixPatternMatch=true(默认开启)时,Spring 会对请求路径进行后缀剥离,尝试匹配无后缀版本。

例如:

  • 请求:/admin/sysNotice/getMyNotice.do
  • 实际匹配:/admin/sysNotice/getMyNotice

因此虽然不能直接命中 urlLookup,但仍可通过 addMatchingMappings 找到目标 Controller。

但这带来了严重的性能代价!


五、性能瓶颈可视化证据

1. 线程栈截图分析


红框部分显示大量调用 getMatchingCondition()、getMatchingPattern()、doMatch() 等方法,正是 addMatchingMappings 中的循环匹配过程。

2. Arthas 性能监控结果


  • 带 .do 的请求耗时接近 80ms
  • 不带 .do 的请求仅需 0.7ms

说明:是否直接命中 urlLookup 决定了性能差异。


六、解决方案建议

方案一:在 @RequestMapping 中显式添加 .do(推荐)

java 复制代码
@RequestMapping("/fileType/findFileList.do")
public ResponseEntity<?> findFileList(...) { ... }

✅ 优点:

  • 直接命中 urlLookup,避免全量扫描
  • 改动小,不影响现有业务逻辑
  • 不影响静态资源访问

❌ 缺点:

  • 接口风格略显冗余
  • 可能需要批量修改注解

👉 适合大多数场景,是最安全、最高效的方案。


方案二:配置 Tomcat 将非 .do 请求也交给 Spring MVC 处理

修改 web.xml 或使用 DispatcherServlet 的 url-pattern 配置:

xml 复制代码
<servlet-mapping>
    <servlet-name>SpringMvc</servlet-name>
    <url-pattern>/</url-pattern> <!-- 匹配所有请求 -->
</servlet-mapping>

⚠️ 注意事项:

  • 静态资源(JS/CSS/IMG)也会被 Spring MVC 拦截,可能导致 404 或异常
  • 需配合 ResourceHttpRequestHandler 或 DefaultServlet 解决静态资源问题
  • 可能引入额外的过滤器干扰

👉 风险较高,需谨慎测试,建议仅在可控环境下使用。


七、最佳实践建议

  1. 统一接口命名规范 :明确是否使用 .do 后缀,并在整个项目中保持一致。
  2. 优先使用精确匹配 :尽量让请求路径与 @RequestMapping 完全一致,避免依赖后缀匹配。
  3. 监控关键路径性能 :利用 Arthas、SkyWalking 等工具监控 getHandler() 方法耗时。
  4. 避免大规模路径匹配:若系统有上万个接口,应避免频繁触发全量扫描。

八、总结

关键点 说明
问题本质 请求路径含 .do,但注册路径不含,导致无法命中 urlLookup
性能影响 触发全量路径扫描,耗时从 1ms 升至 78ms
根本原因 Spring MVC 后缀匹配机制虽能工作,但效率低下
解决方案 ✅ 显式添加 .do 到 @RequestMapping;或 ⚠️ 修改 url-pattern
最佳实践 统一接口风格,避免依赖模糊匹配
相关推荐
用AI来创业吧8 分钟前
网站建设公司哪家好:报价差 3 倍,差在哪四项成本上(选型笔记)
笔记·性能优化·软件工程·安全威胁分析
青春不朽51217 分钟前
Python 里 a is b 有时 True 有时 False?小整数缓存和常量折叠的底层真相
python·spring·缓存
苏渡苇35 分钟前
Spring Insight:跨服务调用链路是怎么串起来的?
spring boot·后端·spring·系统监控·apm
夕除2 小时前
redis--主从还是集群,内存
java·spring·mybatis
Mikko72 小时前
Spring Boot 3.5.16 自带的 Tomcat 10.1.55 中了哪些 CVE?其中一条是修上一个漏洞时带进来的
java·spring boot·后端·安全·tomcat
霸道流氓气质3 小时前
AI 简历制作平台:结构化输出解析、多模板渲染与 PDF 导出的 Vue3 + Spring AI 全栈实战
人工智能·spring·pdf
打工仔折腾 AI3 小时前
Spring AI + Qwen 周报生成实战:从原始日志到人工确认卡
人工智能·后端·python·spring·elasticsearch·ai agent 实战
yunwei373 小时前
eBPF 入门实践教程:BPF Token,安全的委托式权限与程序加载
linux·后端·性能优化
开开心心就好3 小时前
手机电池健康怎么查?看容量温度循环次数
spring·智能手机·ffmpeg·计算机外设·电脑·maven·音视频
云和数据.ChenGuang3 小时前
Spring AI 对接 LangGraph:构建企业级智能体工作流实战
java·人工智能·spring boot·后端·spring