本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。
Feign 接口中的类注解 @RequestMapping 被 SpringMVC 加载问题
以下两种情况的 Feign 接口都会有这个问题
1) 实现 API 接口
@RequestMapping("/index")
public interface IndexControllerApi {
@DeleteMapping("/haha")
String index(@RequestParam("name") String name);
}
如上要调用一个 API 接口, 创建一个接口继承它, 加上 @FeignClient 指定相应的服务
@FeignClient(value = "producer")
public interface ProducerService extends IndexControllerApi {}
2) 直接定义 Feign 接口
@FeignClient(value = "producer")
@RequestMapping("/index")
public interface IndexControllerApi {
@GetMapping("/haha")
String index(@RequestParam("name") String name);
}
当我们在接口上使用 @RequestMapping 定义一个前缀路径时, SpringBoot 启动过程中, SpringMVC 会将此接口解析加入容器, 上述接口的映射链接为 /index/haha
, 若接口消费方也定义了一个 /index/haha
SpringMVC 接口时, 就会在 SpringMVC 加载失败问题, 异常大致如下:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'requestMappingHandlerMapping'
defined in class path resource [org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]: Invocation of init method failed;
nested exception is java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'cat.wars.course.cloud.consumer.service.IndexControllerApi' method
我在搜索引擎上搜索时, 在 Github 的 spring-cloud-netflix 项目里找到了讨论此问题 Issue, 2016 至今依旧没有官方解决方案:
https://github.com/spring-cloud/spring-cloud-netflix/issues/466
https://github.com/spring-projects/spring-framework/issues/22154
3) 解决方案
3.1) 方案1 - Configuration
从 SpringMVC 下手, 定义如下 Configuration
@Configuration
@ConditionalOnClass({Feign.class})
public class FeignConfiguration {
@Bean
public WebMvcRegistrations feignWebRegistrations() {
return new WebMvcRegistrations() {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new FeignRequestMappingHandlerMapping();
}
};
}
private static class FeignRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(@NonNull Class<?> beanType) {
return super.isHandler(beanType) &&
!AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class);
}
}
}
3.2) 方案2 - 使用 @FeignClieng 代替 @RequestMapping
缺点是当子父接口都需要使用 @FeignClient 接口时会有问题, Feign 只会解析子接口的 @FeignClient
@FeignClient(value = "producer", path="/index")
public interface IndexControllerApi {
@GetMapping("/haha")
String index(@RequestParam("name") String name);
}