FeignClient 出现 Ambiguous mapping 重复映射

场景复现

依赖:

  1. Spring Boot 2.1.6.RELEASE
  2. Eureka Client 2.1.0.RELEASE
  3. OpenFeign 2.1.0.RELEASE

我们创建两个项目, ahao-server服务提供方和ahao-client服务调用方.
Eureka可以使用我弄的一个开箱即用Eureka

ahao-server创建一个显示当前时间的controller, 同时注册到eureka上.
假设端口为http://localhost:8080

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/ahao")
public class TimeController {
@RequestMapping("/now")
public String nowTime() {
return "现在时间是:" + new SimpleDateFormat("yyyy年MM月dd日 hh时mm分ss秒").format(new Date());
}
}

ahao-client创建一个controller和一个feign客户端, 同时注册到eureka上.
假设端口为http://localhost:8081

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/ahao")
public class TimeController {
@Autowired
private TimeApi timeApi;
@RequestMapping("/now")
public String now() {
return timeApi.nowTime();
}
}

@FeignClient(value = "AHAO-SERVER")
@RequestMapping("/ahao")
public interface TimeApi {
@RequestMapping("/now")
String nowTime();
}

运行ahao-client报错java.lang.IllegalStateException: Ambiguous mapping.
我们改一下ahao-clientcontroller.

1
2
3
4
5
6
7
8
9
10
@RestController
@RequestMapping("/ahao")
public class TimeController {
@Autowired
private TimeApi timeApi;
@RequestMapping("/my-now")
public String now() {
return timeApi.nowTime();
}
}

再运行ahao-client就可以了.
我们访问http://localhost:8081/ahao/my-now可以得到ahao-server提供的时间服务, 但是访问http://localhost:8081/ahao/now就是404了.
那为什么会出现java.lang.IllegalStateException: Ambiguous mapping.呢?

问题所在

我们看下RequestMappingHandlerMapping映射注册器

1
2
3
4
5
6
7
8
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping implements MatchableHandlerMapping, EmbeddedValueResolverAware {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
}

也就是, 只要Bean类上有@Controller注解或者@RequestMapping注解, 那就会解析url映射.
我们的TimeApi上刚好有一个@RequestMapping注解. 所以TimeApiTimeController才会出现url映射冲突.

我们改造成my-now后, 就会有两个映射关系

  1. /ahao/my-now: 正常的访问ahao-server服务
  2. /ahao/now: 访问失败404

看到这里肯定有疑问了, 为什么/ahao/nowurl映射关系, 访问却404?
我们再改造下TimeApi. 加个@ResponseBody注解, 看到这里应该就知道Spring MVC做了多大的一件蠢事.

1
2
3
4
5
6
7
@FeignClient(value = "AHAO-SERVER")
@RequestMapping("/ahao")
@ResponseBody
public interface TimeApi {
@RequestMapping("/now")
String nowTime();
}

现在两个url都可以正常访问了

  1. /ahao/my-now: 正常的访问ahao-server服务
  2. /ahao/now: 正常的访问ahao-server服务

解决方案

这是Spring MVC的锅, Feign是不可能改的了, 而且Spring MVC也不可能改, 因为要兼容以前版本的使用者.

最简单的方法

不要把@RequestMapping@FeignClient一起用, 直接把链接拼接到方法级的@RequestMapping

1
2
3
4
5
@FeignClient(value = "AHAO-SERVER")
public interface TimeApi {
@RequestMapping("/ahao/now")
String nowTime();
}

或者用@FeignClientpath属性

1
2
3
4
5
@FeignClient(value = "AHAO-SERVER", path = "/ahao")
public interface TimeApi {
@RequestMapping("/now")
String nowTime();
}

装逼用方法

来源: https://github.com/spring-cloud/spring-cloud-netflix/issues/466#issuecomment-257043631
但是失去了自动装配的一些特性, 不推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Configuration
@ConditionalOnClass({Feign.class})
public class FeignMappingDefaultConfiguration {
@Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrationsAdapter() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new FeignFilterRequestMappingHandlerMapping();
}
};
}

private static class FeignFilterRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(Class<?> beanType) {
return super.isHandler(beanType) && (AnnotationUtils.findAnnotation(beanType, FeignClient.class) == null);
}
}
}

参考资料