前言
最近在搭建Spring Cloud 架构,将几个系统建立成一个微服务体系。
其中用到feign作为远程调用的工具,将controller提取成接口(并在这些接口上加上@FeignClient 和@RequestMapping 注解),并将这些接口单独迁移到一个maven 项目,好让其他项目引用就可以直接调用远程服务。
假设依赖关系
假设有 A、B服务及C模块:
A 抽象出接口形成C模块 ,提供B 使用;
B 通过 带有@FeignClient 声明的C接口模块 调用A服务;
C接口如下:
@FeignClient("user-api")
@RequestMapping("/v1/user")
public interface UserAPI{
/**
* 获取所有用户
/*
@RequestMapping("/list")
public Collection<User> getAll();
}
A代码如下:
@RestController
public class UserController implements UserAPI{
@Override
public Collection<User> getAll(){
// 查询用户代码
}
}
B代码如下:
//引用C 接口模块的UserAPI
import UserAPI;
@RestController
@SpringBootApplication
@EnableFeignClients
public class MySpringBootApplication{
public static void main(String[] args) {
SpringApplication.run(BootApplication.class, args);
}
@Autowired
UserAPI userAPI;
@RequestMapping("/test")
public Object getSome(){
//调用userAPI
return userAPI.getAll();
}
}
题外话:留意一下A模块的UserController ,实现了UserAPI 的时候,可以直接依赖UserAPI所以定义的RequestMapping作映射处理,spring
mvc 可以正常扫描处理。
问题
当其B项目引用C 接口模块的时候,会将这些接口映射成handler,也就是说可以这些接口所定义的HTTP请求都可以访问,比如UserAPI定义的url请求:
/v1/user/list
意思B 项目启动的时候,会带有A项目所定义的url处理。
但因为B项目没有具体的实现,所以直接访问会报错,因为spring 只作了一层代理,但这个代理没有找到具体的实现。
@RequestMapping注解
C模块的UserAPI,只加了@RequestMapping,而没有@Controller注解 为什么Spring Boot会把它扫描成Controller?
原因是@RequestMapping 是在所有IOC容器的bean 初始化以后进行扫描带有该注解的bean。 而加上了@FeignClient 注解后会实例化一个FeignClient 代理,即使这里没有@Controoler 去创建一个IOC 容器的bean ,最后就正常当做Controller处理。
RequestMappingHandlerMapping类
这个类有一个方法:
public boolean isHandler(Class<?> beanType){
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
用于判断扫描的bean是否属于一个spring mvc 中的 handler,如果是则映射其定义的url处理。
所以只要改写这个类就可以了。
重写RequestMappingHandlerMapping类
在Spring Boot里,有一个WebMvcAutoConfiguration类专门加初始化spring mvc 需要的bean,里面有一个内部类,也是打上@Configuration注解的:
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping();
}
@Override
protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {
if (this.mvcRegistrations != null
&& this.mvcRegistrations.getRequestMappingHandlerMapping() != null) {
return this.mvcRegistrations.getRequestMappingHandlerMapping();
}
return super.createRequestMappingHandlerMapping();
}
// 其他代码,略
}
注意这里是createRequestMappingHandlerMapping方法,而不是requestMappingHandlerMapping方法。可以看到createRequestMappingHandlerMapping已经提供了一个机制让我们去扩展,于是找到
public interface WebMvcRegistrations {
default RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return null;
}
default RequestMappingHandlerAdapter getRequestMappingHandlerAdapter() {
return null;
}
default ExceptionHandlerExceptionResolver getExceptionHandlerExceptionResolver() {
return null;
}
}
直接重写这个getRequestMappingHandlerMapping的实现就可以了,所以注入这个Bean:
@Configuration
public class WebMvcRegistrationAdpater implements WebMvcRegistrations {
@Override
public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
return new MyRequestMappingHandlerMapping();
}
public class MyRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
@Override
protected boolean isHandler(Class<?> beanType) {
// 原来逻辑:
/*
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
*/
//去掉RequestMapping
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
( AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class) && beanType.isInterface()==false )
);
}
}
}
到这里就已经解决到问题了。
参考
重写 RequestMappingHandlerMapping
https://blog.csdn.net/he_qiao/article/details/92968613
网友评论