Feign注解方式RPC调用的简单实现

背景

阿里口碑同学主导了一个客户端jar包的项目,跟随P6开发参与其中,项目中使用到了sqlite这种轻量级的数据库,口碑同学直接手写了一个类Mybatis数据库框架用来操作数据库,Mybatis中的mapper类都是接口理论上是不能在spring中注册成bean的,但是为什么我们可以用@Autowired @Resource注解来注入到其他类里呢,其实这个bean是原接口的代理类而已
当然受保密协议影响我不可能将源码贴出来,但是我借鉴其中的实现方式来实现了一个简单的feign调用的demo

实现

概述

我们知道feign是springCloud的组件之一,也是我们经常用到的,它在Ribbon基础上做了使用上的封装,是我们使用起来就像调用本地接口一样方便,当然Ribbon的实现是基于RestTemplate而且做了软负载,我们的demo没有那么多内容,仅是实现了接口形式的RPC调用

注解

我们先准备三个注解如下

RemoteCallScan

这个注解的主要作用是在启动类上配置我们进行rpc调用的service接口的路径
注意这里我们使用@Import注解导入RemoteCallPackageRegistrar这个类,该类在后面会有描述

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Import({RemoteCallPackageRegistrar.class})
public @interface RemoteCallScan {

    @AliasFor(value = "basePackages")
    String[] value() default {};

    @AliasFor(value = "value")
    String[] basePackages() default {};
}
RemoteServer

这个注解用在rpc接口上,相当于@FeignClient注解,指定远程服务名,这里我们做了占位符解析,注解里可以写服务名,将服务名的真正ip配置在application.properties中

/**
  * @description: 远程服务的name
  * @author zjg
  * @date 2020/4/23 16:39
  */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RemoteServer {

    @AliasFor(value = "serverName")
    String value() default "";

    @AliasFor(value = "value")
    String serverName() default "";
}
RemotePath

这个注解用在rpc接口的方法上指定请求的方法路径,相当于@RequestMapping注解

/**
  * @description: 远程调用方法的path
  * @author zjg
  * @date 2020/4/23 16:39
  */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RemotePath {

    @AliasFor(value = "methodUrl")
    String value() default "";
    
    @AliasFor(value = "value")
    String methodUrl() default "";

}

RemoteCallProxy

这个就是接口动态代理类的拦截类,我们在这里拦截rpc方法,获取方法上的注解参数:服务名和请求方法以及请求参数,然拼装成完整的请求

/**
  * @description: 代理类的拦截
  * @author zjg
  * @date 2020/5/1 15:49
  */
@Slf4j
public class RemoteCallProxy implements InvocationHandler {

    private String serverName;

    public RemoteCallProxy(String serverName){
        this.serverName = serverName;
    }


    /**
     * proxy:代理类代理的真实代理对象com.sun.proxy.$Proxy0
     * method:我们所要调用某个对象真实的方法的Method对象
     * args:指代代理对象方法传递的参数
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        RemotePath remotePath = (RemotePath)method.getAnnotation(RemotePath.class);
        AssertUtil.isNotNull(remotePath,"远程访问,remotePath不能为空");
        String methodPath = remotePath.value();
        AssertUtil.isNotBlank(methodPath,"远程访问,methodPath不能为空");
        Class<?> returnType = method.getReturnType();
        AssertUtil.isNotNull(returnType,"远程访问,returnType不能为空");
        AssertUtil.isNotNull(args,"远程访问,args不能为空");

        String url = serverName + "/" + remotePath.value();


        HttpService httpService = SpringContextUtil.getBean(HttpService.class);
        Object result = httpService.post(url, args[0], returnType);
        AssertUtil.isNotNull(result,"远程访问,返回参数不能为空");
        log.info("远程方法url:" + url + "  请求参数:" + args[0] + "  远程服务地址serverName:" + serverName);
        return result;
    }

}
RemoteFactoryBean

改类实现了FactoryBean接口,FactoryBean在spring中的特点是:当我们配置的bean的class指定是FactoryBean的时候,Spring最终给我们生成的这个bean将是FactoryBean中getObject()方法返回的bean


public class RemoteFactoryBean<T> implements FactoryBean<T> {
    private Class<T> remoteCallServiceInterface;

    /**
     * 远程服务名称
     */
    private String remoteServerName;

    public RemoteFactoryBean(Class<T> remoteCallServiceInterface) {
        this.remoteCallServiceInterface = remoteCallServiceInterface;
    }

    @Override
    public T getObject() throws Exception {
        return (T) Proxy.newProxyInstance(this.remoteCallServiceInterface.getClassLoader(), new Class[]{this.remoteCallServiceInterface}, new RemoteCallProxy(remoteServerName));
    }

    @Override
    public Class<?> getObjectType() {
        return this.remoteCallServiceInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }


    public String getRemoteServerName() {
        return this.remoteServerName;
    }

    public void setRemoteServerName(String remoteServerName) {
        this.remoteServerName = remoteServerName;
    }
}

RemoteCallPackageRegistrar

该类是通过@Import注解导入到了RemoteCallScan注解中,@Import注解只能用在类上,里面的值可以是

  1. 定义了bean的Class数组
  2. 实现了ImportSelector接口的类
  3. 实现了ImportBeanDefinitionRegistrar接口的类
    我们使用的就是第三种,在RemoteCallPackageRegistrar继承的registerBeanDefinitions方法中注册一个
    RemoteCallScannerConfigurer的bean,并指定了属性值

/**
  * @description: ImportBeanDefinitionRegistrar在ConfigurationClassPostProcessor
  * 处理Configuration类期间被调用,用来生成该Configuration类所需要的BeanDefinition。
  * 而ConfigurationClassPostProcessor正实现了BeanDefinitionRegistryPostProcessor接口
 *
 * spring启动我们自定义注册的入口
  * @author zjg
  * @date 2020/6/28 19:06
  */
public class RemoteCallPackageRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {
    public RemoteCallPackageRegistrar() {
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
    }

    /**
     * AnnotationMetadata 表示当前被@Import注解给标注的所有注解信息
     * 主要就是向spring中注入了一个RemoteCallScannerConfigurer的beanDefinition,对象名可以自己命名,
     * 在后面RemoteCallScannerConfigurer类执行初始化的时候用到,主要是解析占位符
     */
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        AnnotationAttributes annAttrs = AnnotationAttributes.fromMap(importingClassMetadata
                .getAnnotationAttributes(RemoteCallScan.class.getName()));
        if (null != annAttrs) {
            this.registerBeanDefinitions(annAttrs, registry, generateBaseBeanName(importingClassMetadata, 0));
        }
    }

    void registerBeanDefinitions(AnnotationAttributes annAttrs, BeanDefinitionRegistry registry, String beanName) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(RemoteCallScannerConfigurer.class);
        builder.addPropertyValue("processPropertyPlaceHolders", true);
        List<String> basePackages = new ArrayList();
        basePackages.addAll((Collection)Arrays.stream(annAttrs.getStringArray("value"))
                .filter(StringUtils::hasText).collect(Collectors.toList()));
        basePackages.addAll((Collection)Arrays.stream(annAttrs.getStringArray("basePackages"))
                .filter(StringUtils::hasText).collect(Collectors.toList()));
        //Comma Delimited逗号分隔
        builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));
        registry.registerBeanDefinition(beanName, builder.getBeanDefinition());
    }

    private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
        String str = importingClassMetadata.getClassName() + "#" + RemoteCallScannerConfigurer.class.getSimpleName() + "#" + index;
        return str;
    }

}
RemoteCallScannerConfigurer

该类继承了BeanDefinitionRegistryPostProcessor接口,该接口又继承了BeanFactoryPostProcessor接口,其中有两个方法,postProcessBeanDefinitionRegistry是BeanDefinitionRegistryPostProcessor自带的,postProcessBeanFactory是从BeanFactoryPostProcessor继承过来的。
postProcessBeanFactory方法主要用来对bean定义做一些改变。postProcessBeanDefinitionRegistry是在所有Bean定义信息将要被加载,Bean实例还未创建的时候执行,优先postProcessBeanFactory执行,入参BeanDefinitionRegistry提供了丰富的方法来操作BeanDefinition,判断、注册、移除等方法都准备好了,我们在编写postProcessBeanDefinitionRegistry方法的内容时,就能直接使用入参registry的这些方法来完成判断和注册、移除等操作。
此处的postProcessBeanDefinitionRegistry方法进行了BeanDefinition的扫描,具体的扫描类是下面的ClassPathRemoteCallScanner


/**
  * @description: BeanDefinitionRegistry的后处理器  用来注册额外的BeanDefinition
  * 经常被用来注册BeanFactoryPostProcessor的BeanDefinition。
  * @author zjg
  * @date 2020/5/2 19:03
  */
    public class RemoteCallScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean,
        ApplicationContextAware, BeanNameAware {
    private String beanName;
    private String basePackage;
    private ApplicationContext applicationContext;
    private boolean processPropertyPlaceHolders;

    public RemoteCallScannerConfigurer() {
    }

    @Override
    public void setBeanName(String name) {
        /**
         * RemoteCallPackageRegistrar中generateBaseBeanName生成的beanName
         */
        this.beanName = name;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.basePackage, "Property 'basePackage' is required");
    }

    /**
     * 方法会在所有的BeanDefinition已经被加载了,但是所有的Bean还没有被创建前调用
     * @param beanDefinitionRegistry
     * @throws BeansException
     */
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
        if (this.processPropertyPlaceHolders) {
            /**
             * spring启动容器时,执行的顺序是先执行所有的BeanDefinitionRegistryPostProcessor,
             * 再执行BeanFactoryPostProcessor中的postProcessBeanFactory,在里面会有占位符的替换
             * 所以这里在执行扫描接口时,引用的占位符还没有解析替换成相应的properties中的值
             */
            this.processPropertyPlaceHolders();
        }

        ClassPathRemoteCallScanner scanner = new ClassPathRemoteCallScanner(beanDefinitionRegistry);
        scanner.setResourceLoader(this.applicationContext);
        scanner.registerFilters();
        scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n"));
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    private void processPropertyPlaceHolders() {
        Map<String, PropertyResourceConfigurer> prcs = this.applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
        if (!prcs.isEmpty() && this.applicationContext instanceof ConfigurableApplicationContext) {
            BeanDefinition mapperScannerBean = ((ConfigurableApplicationContext)this.applicationContext).getBeanFactory()
                    .getBeanDefinition(this.beanName);
            DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
            factory.registerBeanDefinition(this.beanName, mapperScannerBean);
            Iterator var4 = prcs.values().iterator();

            while(var4.hasNext()) {
                PropertyResourceConfigurer prc = (PropertyResourceConfigurer)var4.next();
                prc.postProcessBeanFactory(factory);
            }

            PropertyValues values = mapperScannerBean.getPropertyValues();
            this.basePackage = this.updatePropertyValue("basePackage", values);
        }

        Optional var10001 = Optional.ofNullable(this.basePackage);
        Environment var10002 = this.getEnvironment();
        var10002.getClass();
        this.basePackage = (String)var10001.map(str -> var10002.resolvePlaceholders((String) str)).orElse((Object)null);
    }

    private String updatePropertyValue(String propertyName, PropertyValues values) {
        PropertyValue property = values.getPropertyValue(propertyName);
        if (property == null) {
            return null;
        } else {
            Object value = property.getValue();
            if (value == null) {
                return null;
            } else if (value instanceof String) {
                return value.toString();
            } else {
                return value instanceof TypedStringValue ? ((TypedStringValue)value).getValue() : null;
            }
        }
    }

    private Environment getEnvironment() {
        return this.applicationContext.getEnvironment();
    }

    public String getBasePackage() {
        return this.basePackage;
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }
    public boolean isProcessPropertyPlaceHolders() {
        return this.processPropertyPlaceHolders;
    }

    public void setProcessPropertyPlaceHolders(boolean processPropertyPlaceHolders) {
        this.processPropertyPlaceHolders = processPropertyPlaceHolders;
    }
}

ClassPathRemoteCallScanner

该类的作用是扫描isCandidateComponent方法指定的类,将扫描到的这些BeanDefinition重新定义一下,即指定bean的class是RemoteFactoryBean类,并将这些bean上注解的值取出来赋值给属性


/**
  * @description: remote接口的路径扫描
  * @author zjg
  * @date 2020/5/2 17:33
  */
@Slf4j
public class ClassPathRemoteCallScanner extends ClassPathBeanDefinitionScanner implements ApplicationContextAware {
    private ApplicationContext applicationContext;
    private Class<? extends RemoteFactoryBean> remoteFactoryBeanClass = RemoteFactoryBean.class;

    /**
     * 对标注RemoteServer注解的类生成的BeanDefinition进行修改,转为代理类
     * @param basePackages
     * @return
     */
    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

        if (beanDefinitions.isEmpty()) {

            log.warn("No RemoteCall interface was found in '{}' package. Please check your configuration.",
                    Arrays.toString(basePackages));
            return beanDefinitions;
        } else {
            Iterator var3 = beanDefinitions.iterator();
            while(var3.hasNext()) {
                BeanDefinitionHolder holder = (BeanDefinitionHolder)var3.next();
                GenericBeanDefinition definition = (GenericBeanDefinition)holder.getBeanDefinition();
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition)definition;
                AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata();
                this.processBeanDefinitions(definition, metadata);
                log.debug("Creating RemoteCallFactoryBean with name '{}' and '{}' RemoteCall Interface",
                        holder.getBeanName(), definition.getBeanClassName());
            }
            return beanDefinitions;
        }
    }

    private void processBeanDefinitions(GenericBeanDefinition definition, AnnotationMetadata metadata) {
        String beanClassName = definition.getBeanClassName();
        if (!StringUtils.isEmpty(beanClassName)) {
            definition.setBeanClass(this.remoteFactoryBeanClass);
            //创建FactoryBean需要在构造方法中指定class参数
            definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName);
            Map<String, Object> attributesMap = metadata.getAnnotationAttributes(RemoteServer.class.getName());
            if (!CollectionUtils.isEmpty(attributesMap) && null != attributesMap.get("serverName")) {
                String remoteServerName = String.valueOf(attributesMap.get("serverName"));
                if (!StringUtils.isEmpty(remoteServerName)) {
                    //指定MapperFactoryBean的remoteServerName属性值
                    definition.getPropertyValues().add("remoteServerName", remoteServerName);
                }

            }
        }
    }

    /**
     * 指定扫描RemoteServer注解的接口
     * @param beanDefinition
     * @return
     */
    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        AnnotationMetadata metadata = beanDefinition.getMetadata();
        boolean hasAnnotation = metadata.hasAnnotation(RemoteServer.class.getName());
        //isIndependent()  top level class or nested class (静态内部类)
        //指定是接口
        return hasAnnotation && beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
    }

    public ClassPathRemoteCallScanner(BeanDefinitionRegistry registry) {
        super(registry, true);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public void registerFilters() {

        this.addIncludeFilter(new AnnotationTypeFilter(RemoteServer.class));

    }

}

结束

里面的注释已经比较详细了,大家可以结合注释看看代码运行一下

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值