Spring控制器映射歧义:MockMvc报Ambiguous handler methods错误的解决
我定义了如下两个Spring控制器方法:
@PostMapping( value = Endpoints.GET_PRICE, produces = MediaType.APPLICATION_JSON_VALUE, headers = {"Accept=*/*"}) public ResponseEntity<PricesResponse> getPrice( @RequestBody @Validated({PriceRequestValidationCheck.class}) PriceRequest priceRequest) { PricesResponse defaultPriceResponse = priceService.generateAllPricesForWeb(priceRequest, Optional.empty()); return ResponseEntity.ok(defaultPriceResponse); } @PostMapping( value = Endpoints.GET_PRICE, produces = MediaType.APPLICATION_JSON_VALUE, headers = {"Accept=application/vnd.price.v2+json"}) public ResponseEntity<PricesResponse> getPriceV2( @RequestBody @Validated({PriceRequestValidationCheckV2.class}) PriceRequest priceRequest, HttpServletRequest request) { PricesResponse defaultPriceResponse = priceService.generateAllPricesForWeb( priceRequest, Optional.of( request .getHeader("Accept") .replace("application/vnd.price.v", "") .replace("+json", ""))); return ResponseEntity.ok(defaultPriceResponse); }
当执行如下MockMvc测试方法时:
private MvcResult callPriceEndPoint(MediaType acceptHeader) throws Exception { return mockMvc .perform( MockMvcRequestBuilders.post(GET_PRICE_PATH) .content(getJsonString(getPriceRequest())) .contentType(MediaType.APPLICATION_JSON) .accept(acceptHeader)) .andExpect(status().is5xxServerError()) .andReturn(); }
出现了如下错误:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.IllegalStateException: Ambiguous handler methods mapped for '/api/price': {public...
已知Endpoints.GET_PRICE对应路径/api/price,清楚测试方法无法确定应调用哪个控制器方法,请问该如何解决此问题?
解决方案
1. 调整第一个方法的headers匹配规则
第一个方法的headers = {"Accept=*/*"}会匹配所有Accept头,包括application/vnd.price.v2+json,导致Spring无法区分两个方法。可以修改第一个方法的headers,排除带有版本标识的Accept头:
@PostMapping( value = Endpoints.GET_PRICE, produces = MediaType.APPLICATION_JSON_VALUE, headers = {"Accept!=application/vnd.price.v*+json"}) public ResponseEntity<PricesResponse> getPrice(...) { // 原有逻辑 }
或者直接匹配默认的JSON类型,代替通配符:
headers = {"Accept=application/json"}
2. 用produces属性规范版本区分
Spring的produces属性原生支持基于Accept头的内容协商,比手动配置headers更严谨。可以将两个方法的produces分别设置为对应版本的媒体类型:
// V1 默认版本 @PostMapping( value = Endpoints.GET_PRICE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<PricesResponse> getPrice(...) { // 原有逻辑 } // V2 版本 @PostMapping( value = Endpoints.GET_PRICE, produces = "application/vnd.price.v2+json") public ResponseEntity<PricesResponse> getPriceV2(...) { // 原有逻辑 }
此时Spring会自动根据请求的Accept头匹配对应方法:请求头为application/vnd.price.v2+json时调用V2方法,其他JSON类型请求(包括application/json)调用V1方法,不会出现歧义。
3. 测试时避免传入模糊的Accept头
如果保留原有控制器写法,测试时要确保传入的Accept头只会命中一个方法:
- 测试V1时,使用
MediaType.APPLICATION_JSON作为Accept头 - 测试V2时,使用
MediaType.valueOf("application/vnd.price.v2+json")作为Accept头
不要使用MediaType.ALL(即*/*)测试V1,否则会同时匹配两个方法的headers条件。
内容的提问来源于stack exchange,提问作者runnerpaul

