bigywater.tech
Spring · 40

HandlerMapping:请求怎么匹配到 Controller 方法,路径模式与优先级

一个请求怎么找到对应的 Controller 方法?字面量和变量谁优先?末尾斜杠和后缀还能匹配吗?两个方法映射冲突什么时候报错?这篇在 Spring Boot 4.0.8 的 Spring MVC 7.0.9 上用一组请求逐个验证。

500
/users/42/orders 同时匹配带正则和不带正则的两个模式时的状态码(启动时不报错)
404
末尾斜杠 /hello/、后缀 /hello.json、大小写 /Hello 对只写了 /hello 的方法的结果
406 / 415
Accept 或 Content-Type 与 produces / consumes 不匹配时的状态码

优先级

实测 · 本机执行输出
  说明  |  请求  |  状态  |  谁处理的
  字面量 vs 变量  |  GET /users/me  |  200  |  me
  字面量 vs 变量  |  GET /users/42  |  200  |  byId(42)
  正则变量 vs 任意变量  |  GET /users/42/orders  |  500  |  {"timestamp":"2026-10-19T03:28:04.811Z","status":5
  正则变量 vs 任意变量  |  GET /users/bob/orders  |  200  |  ordersAny(bob)
  /** vs 精确  |  GET /files/readme.txt  |  200  |  files/readme.txt
  /** vs 精确  |  GET /files/a/b/c.txt  |  200  |  files/**
  服务端日志里 /users/42/orders 的 500 原因:Ambiguous handler methods mapped for '/users/42/orders': {ordersAny(java.lang.String), ordersNumeric(java.lang.String)}] with root cause

字面量优先于变量,精确路径优先于 /**,带正则的变量并不比普通变量更具体。

默认行为

实测 · 本机执行输出
  说明  |  请求  |  状态  |  响应(404 等是 Boot 的错误页 JSON)
  末尾斜杠  |  GET /hello/  |  404  |  {"timestamp":"2026-10-19T03:28:05.137Z","status":4
  后缀  |  GET /hello.json  |  404  |  {"timestamp":"2026-10-19T03:28:05.139Z","status":4
  大小写  |  GET /Hello  |  404  |  {"timestamp":"2026-10-19T03:28:05.140Z","status":4
  带点的路径变量(没写正则)  |  GET /users/a.b  |  200  |  byId(a.b)
  写了 {name:.+} 的带点变量  |  GET /doc/report.final.pdf  |  200  |  doc(report.final.pdf)
  路径变量里的 %2F  |  GET /users/a%2Fb  |  400  |  <!doctype html><html lang="en"><head><title>HTTP S

末尾斜杠、后缀、大小写都不自动匹配;路径变量里的点保留;%2F 被 Tomcat 拒绝。

更多条件

实测 · 本机执行输出
  说明  |  请求  |  状态  |  谁处理的 / 错误
  Accept: application/json  |  GET /neg  |  200  |  neg-json
  Accept: text/plain  |  GET /neg  |  200  |  neg-text
  Accept: text/html  |  GET /neg  |  406  |  <html><body><h1>Whitelabel Error Page</h1><p>This 
  ?debug=true  |  GET /cond?debug=true  |  200  |  cond(debug=true)
  没有参数  |  GET /cond  |  200  |  cond()
  X-Env: prod  |  GET /hdr  |  200  |  hdr(prod)
  没有头  |  GET /hdr  |  200  |  hdr()
  Content-Type: application/json  |  POST /consume  |  200  |  consume-json
  Content-Type: text/plain  |  POST /consume  |  415  |  {"timestamp":"2026-10-19T03:28:05.170Z","status":4

produces、consumes、params、headers 可以区分同一路径下的方法。

冲突

实测 · 本机执行输出
  两个方法映射到 GET /x/{a} 与 GET /x/{b}:启动成功
  同一个应用启动后请求 GET /x/1 → 500,消息:Ambiguous handler methods mapped for '/x/1': {java.lang.String demo.Main.two(java.lang.String), java.lang.String demo.Main.one(java.lang.String)}] with root cause
  GET /x/**/y(PathPattern 不允许 ** 在中间):启动失败 PatternParseException: {*...} or ** pattern elements should be placed at the start or end of the pattern

歧义映射启动不报错,请求时 500;** 在中间启动就失败。

分步演示

请求匹配与路径模式
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动。用 Spring Boot 4.0.8 的 SpringApplication(spring-boot-starter-webmvc,内嵌 Tomcat 11)+ RequestMappingHandlerMapping(PathPattern 解析器,Boot 4 的默认);每个 Controller 方法返回自己的名字;请求用 Python http.client 发出
    • 没有验证:AntPathMatcher(旧的匹配策略,spring.mvc.pathmatch.matching-strategy);setUseTrailingSlashMatch 之类的开关怎么重新打开末尾斜杠匹配;多个 @RequestMapping 方法的内容协商优先级细节;矩阵变量;API 版本化(Spring 7 的新特性);带 context-path 的情形
    京ICP备2026067502号-1京公网安备11010502063487号