本文采用知识共享 署名-相同方式共享 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);
}