在做微服务开发的时候我们常常使用openFiegn来进行远程调用,那么就会产生下面这段代码
@FeignClient("demo-feign")
public interface DemoFeignClient {
@GetMapping("/test")
void test();
}
@RestController
public class DemoController implements DemoFeignClient {
@Override
public void test() {
System.out.println("test");
}
}
我们在进行springMvc开发的时候,可以在方法上面加上@RequestMapping、@GetMapping等注解,也可以在类上面加上@RequestMapping给当前类里面所有的请求接口统一加上前缀,如下:
@RestController
@RequestMapping("/demo")
public class DemoApiController implements DemoFeignClient {
@Override
public void test() {
System.out.println("test");
}
}
但是细心的我们发现,如果做微服务开发的时候在feignClient 的类上面加上@RequestMapping,我们的程序启动就会报错,如下:
@FeignClient("demo-feign")
@RequestMapping("/demo")
public interface DemoFeignClient {
@GetMapping("/test")
void test();
}
@RestController
public class DemoApiController implements DemoFeignClient {
@Override
public void test() {
System.out.println("test");
}
}
错误信息如下:
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 'com.example.demofeign.DemoFeignApplication$DemoFeignClient' method
com.example.demofeign.DemoFeignApplication$DemoFeignClient#test()
to {GET [/demo/test]}: There is already 'com.example.demofeign.DemoFeignApplication$DemoApiController' bean method
com.example.demofeign.DemoFeignApplication$DemoApiController#test() mapped.
如果只在方法上加却不会出现这个问题,那么是什么原因导致这个报错呢。
这个问题的产生和springMvc里面怎么判断当前类是否为controller 有关。
AbstractHandlerMethodMapping 这个类会在被spring实例化之后,获取到spring内所有的bean,然后逐个判断当前Bean是否为controller ,而判断的依据就是当前类是否有@Controller注解或者@RequestMapping注解
// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
修复这个问题有2种方式:
1.声明了@FeignClient的接口不要再在类上面加上@RequestMapping注解了
2.创建DemoWebMvcRegistrations 类 继承RequestMappingHandlerMapping并实现WebMvcRegistrations,然后重写里面的isHandler和getRequestMappingHandlerMapping方法
@Component
public class DemoWebMvcRegistrations implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new DemoRequestMappingHandlerMapping();
}
}
/** 注意这个地方不能直接将bean 直接注入到spring 环境,
* 否则你会发现里面每一个controller 都会被映射2次
* @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.MappingRegistry#getMappingsByUrl
**/
public class DemoRequestMappingHandlerMapping extends RequestMappingHandlerMapping implements WebMvcRegistrations {
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
// 当前类上存在RequestMapping 但是不存在FeignClient注解
(AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)
&& !AnnotatedElementUtils.hasAnnotation(beanType, FeignClient.class)
));
}
}