FeignClient服务端,Controller与它的interface们

关键词: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;
    }

}
  • interface中的Demo,和Controller中Demo类是同一个,两边无论是谁增减字段,对方都会立即知道;
  • interface的方法上入参类型、数量、顺序、响应类型发生变化,Controller类也必须对应变化;

而且以上代码可以正常启动,并且URL能正常映射到Controller的方法上:
但是就是不能正常运行――

假的,其实部分还是可以用的


通过swagger生成的文档:
FeignClient服务端,Controller与它的interface们_第1张图片
FeignClient服务端,Controller与它的interface们_第2张图片
/server/demo44对应的demo4方法,namemark明显是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;
    }
 
    ...
    ...
  
}

Mapped映射到抽象方法上

还有一些其他代码等等。启动,正常!swagger,也正常!
貌似没有问题了~


@RestController标记在interface上,重写扫描事件,容许扫描interface。在生成Mapped的时候,此时动态代理的代码块还没执行,spring容器中没有该interface的实现类,因此Mapped只能映射到interface的方法上。然后CatCatCtrlFactoryBean执行,动态代理将实现类和interface关联起来,并且返回实现类。然后Controller层也能通过interface,在Spring容器中获取到实现类了


但是,等等!

  1. CatClientRemoteApi4 (interface) 这个文件是放在Jar包中
  2. 服务端引用这个Jar
  3. 服务端的DemoCatCtrl4 (实现类) 使用了这个Jar包中的CatClientRemoteApi4文件
  4. 但是CatClientRemoteApi4文件,又反向引用了DemoCatCtrl4

一个解决方法是,将@CatApiCtrl中value换成String,存放类全路径字符串即可。


B-U-T !

CatClientRemoteApi4 (interface) 作为一个公共的代码存放在Jar包中,现在写服务端却要改它 (在它上面加个注解),如果服务端的Controller名字发生变化、或者有新的子类扩展它,CatClientRemoteApi4 (interface) 又要改动!

服务端和客户端在一些无关紧要的地方耦合在一起了!




所以说以上方法都是垃圾


3 实验得出结论是,Mapped是可以映射到:抽象类的抽象方法、或者是interface的方法上。
1 实验失败的原因是,Mapped映射到实现类的方法上。
那么,如果保留 1 的写法,那么需要解决的问题就是:如何让Mapped映射到interface上,而不是实现类?


研究表明,需要以下几个步骤:

  1. 在实现类上加自定义注解@CatApiCtrl
  2. 开启自定义扫描事件,扫描包含@CatApiCtrl注解的类
  3. 获取这些类的interface
  4. 将这些interface也注册成组件
  5. 在Spring所有bean加载完毕之后,手动将上述的interface注册成Mapped



开启关闭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,如果出现版本不兼容情况,请下方留言





你可能感兴趣的:(java)