关键词:FeignClient
大家好,我是入错行的bug猫。(http://blog.csdn.net/qq_41399429,谢绝转载)
上一篇写到使用动态代理,仿写一个了FeignClient
客户端(可以戳这里),顺便学习了FeignClient
、自定义注解扫描
、FactoryBean
、动态代理
。
那么,FeignClient
服务端,其实也就是远程服务的Controller。
一般用得比较多的是restful
风格提供接口,响应为json字符串。
使用dubbo的时,服务端会有一个dubbo的配置xml文件,暴露提供的Service层信息(高版本中直接可以使用注解申明)
然后在服务端直接使用一个类实现这个interface接口,实现接口中方法,即可被远程调用。(当然还需要配合注册中心)
看上去就好像客户端,注入一个Service的interface,就完成了调用远程服务端的Service实现类!
客户端 调用 服务端
服务消费者 ───────────────────────────────────> 服务提供者
│ │
│ 注入 │
│ │
└────────────── Service interface │
│ │
│ 实现 │
└───────────────────────┤
│
│
Service 实现类
然后服务端和客户端,直接通过Service interface耦合在一起,如果服务端输入模型、响应模型中,新增了一个字段,那么同版本的客户端可以直接使用这个字段了。
现在用restful
风格的Controller作为服务端代码,服务端和客户端,分别有自己的输入输出数据模型,通过Json字符串耦合在一起。
如果服务端响应,增加了一个属性,但是客户端不做改动的话,客户端是无法使用这个字段的!
虽然这样做,可以说是为了解耦合。
但是怎么办,FeignClient
也好想像dubbo一样, (ಥ_ಥ) 人家也想一呼百应,改一个地方,其他客户端都能集中一起改动,避免在各个客户端中手动重复添加代码、甚至还有些地方改漏了!
先来个 FeignClient 客户端的接口
//@FeignClient() FeignClient的jar包我没有引入,假设已经有了。有没有这个注解不影响结果
public interface CatClientRemoteApi4 {
@ApiOperation(value = "demo41 value", notes = "demo41 notes") //这是swagger注解,无需关注
@RequestMapping(value = "/server/demo41", method = RequestMethod.POST)
Demo demo1(@RequestBody Demo req);
@ApiOperation(value = "demo43 value", notes = "demo43 notes")
@RequestMapping(value = "/server/demo43", method = RequestMethod.GET)
PageInfo demo3(@ModelAttribute Demo req); //通过键值对发送对象,要使用@ModelAttribute注解,否则会被swagger视为RequestBody方式发送json字符串
@ApiOperation(value = "demo44 value", notes = "demo44 notes")
@RequestMapping(value = "/server/demo44", method = RequestMethod.GET)
ResponseEntity demo4(@RequestParam("userName") String name, @RequestParam("userMark") String mark);
@ApiOperation(value = "demo46 value", notes = "demo46 notes")
@RequestMapping(value = "/server/demo46/{uid}", method = RequestMethod.GET)
Void demo6(@PathVariable("uid") Long userId, @RequestParam("userName") String name);
}
1. 如果服务端Controller类,直接实现FeignClient
的interface,不也是耦合在一起了?
@Api
@RestController
public class DemoCatCtrl4 implements CatClientRemoteApi4 {
@Override
public Demo demo1(Demo req) {
return null;
}
@Override
public PageInfo demo3(Demo req) {
return null;
}
@Override
public ResponseEntity demo4(String name, String mark) {
return null;
}
@Override
public Void demo6(Long userId, String name) {
return null;
}
}
而且以上代码可以正常启动,并且URL能正常映射到Controller的方法上:
但是就是不能正常运行――
假的,其实部分还是可以用的
通过swagger生成的文档:
/server/demo44
对应的demo4方法,name、mark明显是query类型参数,但是swagger上却变成了body类型。
同样/server/demo46
对应的demo6,uid是path类型参数,也变成了body类型 (甚至参数名称都错了)。
另外demo1方法根本接收不到入参…
原因是,interface方法上的注解:@RequestBody、@ModelAttribute、@RequestParam、@PathVariable,没有元注解@Inherited。这些注解是作用于方法上,子类如果重写、或者实现了改方法,子类是无法继承到这些注解的!
Controller在扫描注册Mapped时,扫描到的是实现类上的方法,因此无法获取到以上4种注解。swagger在生成文档时,同样也无法获取到这4种注解。导致生成的文档,参数类型、参数名称都不正确。
2. emmmmmmmm 如果把@RestController放在interface上会肿么样呢?
@RestController
//@FeignClient() FeignClient的jar包我没有引入,假设已经有了。有没有这个注解不影响结果
public interface CatClientRemoteApi4 {
@ApiOperation(value = "demo41 value", notes = "demo41 notes")
@RequestMapping(value = "/server/demo41", method = RequestMethod.POST)
Demo demo1(@RequestBody Demo req);
@ApiOperation(value = "demo43 value", notes = "demo43 notes")
@RequestMapping(value = "/server/demo43", method = RequestMethod.GET)
PageInfo demo3(@ModelAttribute Demo req); //通过键值对发送对象,要使用@ModelAttribute注解,否则会被swagger视为RequestBody方式发送json字符串
@ApiOperation(value = "demo44 value", notes = "demo44 notes")
@RequestMapping(value = "/server/demo44", method = RequestMethod.GET)
ResponseEntity demo4(@RequestParam("userName") String name, @RequestParam("userMark") String mark);
@ApiOperation(value = "demo46 value", notes = "demo46 notes")
@RequestMapping(value = "/server/demo46/{uid}", method = RequestMethod.GET)
Void demo6(@PathVariable("uid") Long userId, @RequestParam("userName") String name);
}
还是可以正常启动,想不到吧?!只不过没有没有扫描到Mapped…
因为spring默认组件扫描配置,会忽略孤立的interface的!如果此时在DemoCatCtrl4类上加上@Component,又可以正常扫描到Mapped
//@see: ClassPathScanningCandidateComponentProvider
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
AnnotationMetadata metadata = beanDefinition.getMetadata();
return (metadata.isIndependent() && (metadata.isConcrete() ||
(metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName()))));
}
会提示:Ignored because not a concrete top-level class: CatClientRemoteApi4
不过最终Mapped,仍然映射到实现类的方法上了,swagger效果和第一版一样…
为什么在实现类上加@Component,又可以扫描到?
因为在实现类上加@Component,Spring会生成一个FactoryBean
工厂,专门用来创建实现类对象。在Srping扫描到interface后,会检查能否根据interface,在Spring容器中获取到组件。
同样,在做Mapped映射时,虽然@RestController加在interface上,但是同样会根据interface在Spring容器中获取实现类,如果获取到了,就直接映射到实现类上的方法。
3. 如果使用动态代理呢?就像@FeignClient
里面的fallback操作?
@Api
@CatApiCtrl(DemoCatCtrl4.class) //DemoCatCtrl4为实现类
//@FeignClient() FeignClient的jar包我没有引入,假设已经有了。有没有这个注解不影响结果
public interface CatClientRemoteApi4 {
@ApiOperation(value = "demo41 value", notes = "demo41 notes")
@RequestMapping(value = "/server/demo41", method = RequestMethod.POST)
Demo demo1(@RequestBody Demo req);
@ApiOperation(value = "demo43 value", notes = "demo43 notes")
@RequestMapping(value = "/server/demo43", method = RequestMethod.GET)
PageInfo demo3(@ModelAttribute Demo req); //通过键值对发送对象,要使用@ModelAttribute注解,否则会被swagger视为RequestBody方式发送json字符串
@ApiOperation(value = "demo44 value", notes = "demo44 notes")
@RequestMapping(value = "/server/demo44", method = RequestMethod.GET)
ResponseEntity demo4(@RequestParam("userName") String name, @RequestParam("userMark") String mark);
@ApiOperation(value = "demo46 value", notes = "demo46 notes")
@RequestMapping(value = "/server/demo46/{uid}", method = RequestMethod.GET)
Void demo6(@PathVariable("uid") Long userId, @RequestParam("userName") String name);
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RestController
public @interface CatApiCtrl {
Class value(); //Controller的实现类
}
public class DemoCatCtrl4 implements CatClientRemoteApi4 {
@Override
public Demo demo1(Demo req) {
return null;
}
@Override
public PageInfo demo3(Demo req) {
return null;
}
@Override
public ResponseEntity demo4(String name, String mark) {
return null;
}
@Override
public Void demo6(Long userId, String name) {
return null;
}
}
public class CatCatCtrlFactoryBean implements FactoryBean, InitializingBean, ApplicationContextAware {
...
...
/**
* 解析interface方法,生成动态代理类
*/
private T register () {
Class[] interfaces = new Class[] { clazz }; //被CatApiCtrl标记的interface
CallbackHelper helper = new CallbackHelper(catApiInfo.getCtrlClass(), interfaces) { //catApiInfo.getCtrlClass() Controller的实现类
@Override
protected Object getCallback (Method method) {
return new MethodInterceptor() {
@Override
public Object intercept (Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
};
}
};
Enhancer enhancer = new Enhancer();
enhancer.setInterfaces(interfaces);
enhancer.setSuperclass(catApiInfo.getCtrlClass());
enhancer.setCallbackFilter(helper);
enhancer.setCallbacks(helper.getCallbacks());
Object obj = enhancer.create();
return (T) obj;
}
...
...
}
还有一些其他代码等等。启动,正常!swagger,也正常!
貌似没有问题了~
@RestController标记在interface上,重写扫描事件,容许扫描interface。在生成Mapped的时候,此时动态代理的代码块还没执行,spring容器中没有该interface的实现类,因此Mapped只能映射到interface的方法上。然后CatCatCtrlFactoryBean执行,动态代理将实现类和interface关联起来,并且返回实现类。然后Controller层也能通过interface,在Spring容器中获取到实现类了
但是,等等!
CatClientRemoteApi4
(interface) 这个文件是放在Jar包中DemoCatCtrl4
(实现类) 使用了这个Jar包中的CatClientRemoteApi4
文件CatClientRemoteApi4
文件,又反向引用了DemoCatCtrl4
!一个解决方法是,将@CatApiCtrl中value换成String,存放类全路径字符串即可。
B-U-T !
CatClientRemoteApi4
(interface) 作为一个公共的代码存放在Jar包中,现在写服务端却要改它 (在它上面加个注解),如果服务端的Controller名字发生变化、或者有新的子类扩展它,CatClientRemoteApi4
(interface) 又要改动!
服务端和客户端在一些无关紧要的地方耦合在一起了!
所以说以上方法都是垃圾
3 实验得出结论是,Mapped是可以映射到:抽象类的抽象方法、或者是interface的方法上。
1 实验失败的原因是,Mapped映射到实现类的方法上。
那么,如果保留 1 的写法,那么需要解决的问题就是:如何让Mapped映射到interface上,而不是实现类?
研究表明,需要以下几个步骤:
开启关闭FeignClient服务端模式注解 一般放在SpringBoot启动类上
/**
* 开启FeignClient服务端模式
* @author:
* */
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AnnotationScannerRegistrar.class) // AnnotationScannerRegistrar 核心类,注册扫描自定义注解
public @interface EnableCatApiCtrl {
/**
* 扫描包路径
* */
String[] value() default "";
}
标记Controller服务端注解 Controller实现类
/**
*
* 标记服务端Controller
* @see DemoCatCtrl4
*
* @author: bugcat
* */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
@ResponseBody
public @interface CatApiCtrl {
/**
* 被标记的Controller id别名
* */
@AliasFor(annotation = Component.class, attribute = "value")
String value() default "";
}
自定义扫描事件 通过@EnableCatApiCtrl导入引用
/**
* 扫描自定义注解
*
* 注意,在装载此类时,其生命周期早于Spring容器,任何自动注入注解都是无效的!包括@Autowired @Resource @Value
*
* @author: bugcat
* */
public class AnnotationScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
//资源加载器
private ResourceLoader resourceLoader;
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
/**
* 注册扫描事件
* */
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 这个类AnnotationScannerRegistrar,通过@EnableCatApiCtrl注解上使用@Import加载
// metadata就是被@EnableCatApiCtrl注解的对象,即:启动类
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(metadata.getAnnotationAttributes(EnableCatApiCtrl.class.getName()));
String[] pkgs = annoAttrs.getStringArray("value");
if( pkgs.length == 1 && CatToosUtil.isBlank(pkgs[0]) ){//如果没有设置扫描包路径,取启动类路径
StandardAnnotationMetadata annotationMetadata = (StandardAnnotationMetadata) metadata;
Class> stratClass = annotationMetadata.getIntrospectedClass(); //启动类class
String basePackage = stratClass.getPackage().getName();
pkgs = new String[] {basePackage}; //获取启动类所在包路径
}
// 定义扫描对象
CatApiCtrlScanner scanner = new CatApiCtrlScanner(registry);
scanner.setResourceLoader(resourceLoader);
scanner.addIncludeFilter(new AnnotationTypeFilter(CatApiCtrl.class)); //筛选带有@CatApiCtrl注解的类
//扫描后,遍历扫描的class信息,转换成工厂
scanner.setForEach((holder) -> {
register(holder, registry); //扫描后处理
});
//执行扫描
scanner.scan(pkgs);
}
/**
* 注册工厂
* */
private void register(BeanDefinitionHolder holder, BeanDefinitionRegistry registry) {
GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
AnnotationMetadata beanMetadata = ((AnnotatedBeanDefinition) definition).getMetadata();
AnnotationAttributes attributes = AnnotationAttributes.fromMap(beanMetadata.getAnnotationAttributes(CatApiCtrl.class.getName()));
CatApiCtrlInfo ctrlInfo = new CatApiCtrlInfo(attributes);
try {
String className = definition.getBeanClassName(); //扫描到的interface类名
Class> implClass = resourceLoader.getClassLoader().loadClass(className); //通过类加载器,加载class
definition.setBeanClass(implClass); //FactoryBean类型
definition.setPrimary(true);
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //生成的对象,支持@Autowire自动注入
Class>[] interfaces = implClass.getInterfaces(); //被@CatApiCtrl标记的类的接口
if( interfaces != null ){
for(Class face : interfaces){ //配置工厂,将interface也注册成Spring组件
RootBeanDefinition apiBean = new RootBeanDefinition(face);
apiBean.getPropertyValues().addPropertyValue("ctrlInfo", ctrlInfo);
apiBean.getPropertyValues().addPropertyValue("beanClass", className);
apiBean.getConstructorArgumentValues().addGenericArgumentValue(face.getName());
apiBean.setBeanClass(CatApiCtrlBeans.class);
apiBean.setPrimary(true);
apiBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); //生成的对象,支持@Autowire自动注入
BeanDefinitionHolder apiholder = new BeanDefinitionHolder(apiBean, CatToosUtil.uncapitalize(face.getSimpleName()));
BeanDefinitionReaderUtils.registerBeanDefinition(apiholder, registry);
}
}
} catch ( Exception e ) {
e.printStackTrace();
}
}
private static class CatApiCtrlScanner extends ClassPathBeanDefinitionScanner {
private Consumer forEach;
private BeanDefinitionRegistry registry;
public CatApiCtrlScanner(BeanDefinitionRegistry registry) {
super(registry);
this.registry = registry;
}
@Override
protected Set doScan(String... basePackages) {
//得到所有标记了@CatApiCtrl的类
Set holders = super.doScan(basePackages);
for ( BeanDefinitionHolder holder : holders ){
forEach.accept(holder); //遍历
}
return holders;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
boolean isCandidate = false;
AnnotationMetadata metadata = beanDefinition.getMetadata();
if (metadata.isIndependent()) {
if ( !metadata.isAnnotation() && metadata.hasAnnotation(CatApiCtrl.class.getName())) {
isCandidate = true;
}
}
return isCandidate;
}
protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) throws IllegalStateException {
if( registry.containsBeanDefinition(beanName) ){ //如果@CatApiCtrl标记的类被其他模块扫描,移除其他模块扫描配置
registry.removeBeanDefinition(beanName);
}
return super.checkCandidate(beanName, beanDefinition);
}
public void setForEach(Consumer forEach) {
this.forEach = forEach;
}
}
}
在Srping容器所有bean注册完成之后,执行的方法 InitializingBean.afterPropertiesSet
/**
* 将interface注册成Controller,手动添加Mapped映射
* @author: bugcat
* */
public class CatApiCtrlBeans implements InitializingBean {
private Class beanClass; //使用@CatApiCtrl标记的实现类
private Class clazz; //实现类的interface,应该是FeignClient客户端
private CatApiCtrlInfo ctrlInfo;
@Autowired
private RequestMappingHandlerMapping handlerMapping;
@Override
public void afterPropertiesSet() {
//注册成Controller,url映射到方法
String beanName = CatToosUtil.defaultIfBlank(ctrlInfo.getValue(), CatToosUtil.uncapitalize(beanClass.getSimpleName()));
RequestMappingHandler handler = new RequestMappingHandler(handlerMapping);
handler.init();
handler.detectHandlerMethods(beanName, clazz); //使用interface的class作为注册源
handler.destroy();
}
public CatApiCtrlBeans(Class clazz) {
this.clazz = clazz;
}
public CatApiCtrlInfo getCtrlInfo() {
return ctrlInfo;
}
public void setCtrlInfo(CatApiCtrlInfo ctrlInfo) {
this.ctrlInfo = ctrlInfo;
}
public Class getBeanClass() {
return beanClass;
}
public void setBeanClass(Class beanClass) {
this.beanClass = beanClass;
}
private static class RequestMappingHandler {
private Method getMappingForMethod;
private Method registerHandlerMethod;
private RequestMappingHandlerMapping handlerMapping;
public RequestMappingHandler(RequestMappingHandlerMapping handlerMapping) {
this.handlerMapping = handlerMapping;
}
public void init(){
try {
getMappingForMethod = RequestMappingHandlerMapping.class.getDeclaredMethod("getMappingForMethod", Method.class, Class.class);
getMappingForMethod.setAccessible(true);
registerHandlerMethod = AbstractHandlerMethodMapping.class.getDeclaredMethod("registerHandlerMethod", Object.class, Method.class, Object.class);
registerHandlerMethod.setAccessible(true);
} catch ( NoSuchMethodException e ) {
e.printStackTrace();
}
}
public void destroy(){
getMappingForMethod.setAccessible(false);
registerHandlerMethod.setAccessible(false);
}
public void detectHandlerMethods(Object handler, Class clazz){
Map methods = MethodIntrospector.selectMethods(clazz,
new MethodIntrospector.MetadataLookup() {
@Override
public RequestMappingInfo inspect(Method method) {
try {
return getMappingForMethod(method, clazz);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" + clazz.getName() + "]: " + method, ex);
}
}
});
for (Map.Entry entry : methods.entrySet()) {
Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), clazz);
RequestMappingInfo mapping = entry.getValue();
registerHandler(handler, invocableMethod, mapping);
}
}
private RequestMappingInfo getMappingForMethod(Method method, Class> handlerType) {
try {
return (RequestMappingInfo) getMappingForMethod.invoke(handlerMapping, method, handlerType);
} catch ( Exception e ) {
e.printStackTrace();
}
return null;
}
private void registerHandler(Object handler, Method method, RequestMappingInfo mapping) {
try {
registerHandlerMethod.invoke(handlerMapping, handler, method, mapping);
} catch ( Exception e ) {
e.printStackTrace();
}
}
}
}
@CatApiCtrl注解信息
/**
* @CatApiCtrl注解信息
* @author: bugcat
* */
public class CatApiCtrlInfo {
private String value; //@CatApiCtrl标记类,在Spring容器中的别名
public CatApiCtrlInfo(AnnotationAttributes attributes) {
this.value = attributes.getString("value");
}
public String getValue() {
return value;
}
}
标准FeignClient客户端 其实有没有@FeignClient都无所谓了~
//@FeignClient() FeignClient的jar包我没有引入,假设已经有了。有没有这个注解不影响结果
public interface CatClientRemoteApi4 {
@ApiOperation(value = "demo41 value", notes = "demo41 notes") //这是swagger注解,无需关注
@RequestMapping(value = "/server/demo41", method = RequestMethod.POST)
Demo demo1(@RequestBody Demo req);
@ApiOperation(value = "demo43 value", notes = "demo43 notes")
@RequestMapping(value = "/server/demo43", method = RequestMethod.GET)
PageInfo demo3(@ModelAttribute Demo req); //通过键值对发送对象,要使用@ModelAttribute注解,否则会被swagger视为RequestBody方式发送json字符串
@ApiOperation(value = "demo44 value", notes = "demo44 notes")
@RequestMapping(value = "/server/demo44", method = RequestMethod.GET)
ResponseEntity demo4(@RequestParam("userName") String name, @RequestParam("userMark") String mark);
@ApiOperation(value = "demo46 value", notes = "demo46 notes")
@RequestMapping(value = "/server/demo46/{uid}", method = RequestMethod.GET)
Void demo6(@PathVariable("uid") Long userId, @RequestParam("userName") String name);
}
服务端 url、入参名称、数据类型、响应,都是以interface的为准
@Api(tags = "猫脸 服务端Controller") //这是swagger注解,无需关注
@CatApiCtrl
public class DemoCatCtrl4 implements CatClientRemoteApi4 {
@Override
public Demo demo1(Demo req) {
System.out.println(JSONObject.toJSONString(req));
Demo demo = creart();
return demo;
}
@Override
public PageInfo demo3(Demo req) {
System.out.println(JSONObject.toJSONString(req));
Demo resp = creart();
List list = new ArrayList<>();
list.add(resp);
PageInfo info = new PageInfo(1, 10, 1);
info.setList(list);
return info;
}
@Override
public ResponseEntity demo4(String name, String mark) {
System.out.println("{\"name\":\"" + name + "\",\"mark\":\"" + mark + "\"}");
Demo demo = creart();
return ResponseEntity.ok(demo);
}
@Override
public Void demo6(Long userId, String name) {
System.out.println("{\"userId\":\"" + userId + "\",\"name\":\"" + name + "\"}");
return null;
}
private Demo creart(){
Demo demo = new Demo();
demo.setId(4L);
demo.setName("bugcat4");
demo.setMark("ctrl 服务端 4");
return demo;
}
}
最后,swagger扫描地方稍微改造一下
@Configuration
@EnableSwagger2
@ComponentScan("springfox.documentation")
public class SwaggerConfig {
private ApiInfo apiInfo() {
return new ApiInfoBuilder().title("bugcat").description("bugcat")
.termsOfServiceUrl("").version("1.0").build();
}
@Bean
public Docket createApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
// RequestHandlerSelectors.withClassAnnotation(Api.class) 代表扫描:Mapped方法所在的类,是否包含@Api注解,
// 因此需要改造一下
//.apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
.apis(new Predicate(){
@Override
public boolean apply(RequestHandler input) {
Class> beanType = input.getHandlerMethod().getBeanType();
return beanType.isAnnotationPresent(Api.class);
}
})
.paths(PathSelectors.any())
.build();
}
}
就这样,服务端的Controller就好了~ (demo项目启动后,可以在swagger上看到这个Controller提供的接口信息)
如何,是不是和dubbo很相似了?dubbo使用RPC实现数据传输,FeignClient和CatApiCtrl仍然使用Http协议传输Json
那么问题来了,(*・´ω`・)っDemoCatCtrl4
到底是属于控制层,还是业务层呢?
(づ。◕‿‿◕。)づ @CatApiCtrl
不光能和FeignClient
配合使用,甚至普通的Controller,也可以这样写~
└── catctrl:猫脸服务端核心包
│
├── annotation:
│ │
│ ├── CatApiCtrl:标记服务端Controller
│ │
│ └── EnableCatApiCtrl:开启FeignClient服务端模式
│
│
├── beanInfos:
│ │
│ └── CatApiCtrlInfo:CatApiCtrl注解信息
│
│
└── scanner:
│
├── AnnotationScannerRegistrar:注册扫描自定义注解事件,并且注册工厂
│
└── CatApiCtrlBeans:注册Controller
核心类就5个,不到500行代码,炒鸡轻量~
唯一点需要注意的是,服务端引入了CatClientRemoteApi4
这个接口,而且这个接口上有@FeignClient
注解。如果服务端也开启了FeignClient,那么服务端中,这个接口同样会被注册成Feign客户端!所以服务端在使用@EnableFeignClients
时,注意配置扫描包路径即可解决
项目地址:https://gitee.com/qq972245132/cat-client
将CatClientRemoteApi4类中的@RequestMapping注解换成@CatMethod,就可以支持bug猫仿写的FeignClient客户端哟,并且仍然兼容标准版的FeignClient客户端 (◕ω<) ♪
demo使用 Springboot 1.5.18.RELEASE + swagger2 2.5.0,如果出现版本不兼容情况,请下方留言