You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring控制器映射歧义:MockMvc报Ambiguous handler methods错误的解决

解决Spring控制器方法映射歧义问题

我定义了如下两个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 09:13:29