博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
改造Dubbo,使其可以对接口方法进行注解配置
阅读量:6305 次
发布时间:2019-06-22

本文共 10283 字,大约阅读时间需要 34 分钟。

  hot3.png

在之前的文章中介绍过了基于Spring 4 + Dubbo 的注解配置。参见。

但是,在使用中会发现,Dubbo仅仅提供了两个注解:

  • com.alibaba.dubbo.config.annotation.Reference
  • com.alibaba.dubbo.config.annotation.Service

而且,这两个注解中仅仅包含了基本的配置项。导致有一些配置无法通过注解方式完成。比如:<dubbo:method>,这个常用的配置,就没有提供对应的注解。

1.分析

在中,曾经分析过,Dubbo的注解完全是由AnnotationBean来完成的。 那先来看看AnnotationBean是怎么处理配置的:

public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {    ...        Field[] fields = bean.getClass().getDeclaredFields();        for (Field field : fields) {            try {                if (! field.isAccessible()) {                    field.setAccessible(true);                }                Reference reference = field.getAnnotation(Reference.class);            	if (reference != null) {	                Object value = refer(reference, field.getType());	                if (value != null) {	                	field.set(bean, value);	                }            	}            } catch (Throwable e) {            	logger.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);            }        }        ...    }

其实AnnotationBean中有好几处类似这样的代码。其实逻辑都大同小异:

  1. 在类、方法、属性上查找ReferenceService注解;
  2. 将注解转换成对应的com.alibaba.dubbo.config.spring.ReferenceBean或者com.alibaba.dubbo.config.spring.ServiceBean

那再来看看ReferenceBean

public class ReferenceBean
extends ReferenceConfig
implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean

这个是继承ReferenceConfig,而这个ReferenceConfig是带有Method配置信息的:

public class ReferenceConfig
extends AbstractReferenceConfig { ... // 方法配置 private List
methods; ... }

但是注解处理过程中并没有对这个属性进行赋值处理,追踪一下AnnotationBean,就会发现配置对象的处理是交由refer方法完成的:

private Object refer(Reference reference, Class
referenceClass) { //method.getParameterTypes()[0] ... String key = reference.group() + "/" + interfaceName + ":" + reference.version(); ReferenceBean
referenceConfig = referenceConfigs.get(key); if (referenceConfig == null) { referenceConfig = new ReferenceBean(reference); ... } referenceConfigs.putIfAbsent(key, referenceConfig); referenceConfig = referenceConfigs.get(key); } return referenceConfig.get(); }

ReferenceBean的构造器中传入了注解对象,并最终交由com.alibaba.dubbo.config.AbstractConfig#appendAnnotation方法处理:

protected void appendAnnotation(Class
annotationClass, Object annotation) { Method[] methods = annotationClass.getMethods(); for (Method method : methods) { if (method.getDeclaringClass() != Object.class && method.getReturnType() != void.class && method.getParameterTypes().length == 0 && Modifier.isPublic(method.getModifiers()) && ! Modifier.isStatic(method.getModifiers())) { try { String property = method.getName(); if ("interfaceClass".equals(property) || "interfaceName".equals(property)) { property = "interface"; } String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1); Object value = method.invoke(annotation, new Object[0]); if (value != null && ! value.equals(method.getDefaultValue())) { Class
parameterType = ReflectUtils.getBoxedClass(method.getReturnType()); if ("filter".equals(property) || "listener".equals(property)) { parameterType = String.class; value = StringUtils.join((String[]) value, ","); } else if ("parameters".equals(property)) { parameterType = Map.class; value = CollectionUtils.toStringMap((String[]) value); } try { Method setterMethod = getClass().getMethod(setter, new Class
[] { parameterType }); setterMethod.invoke(this, new Object[] { value }); } catch (NoSuchMethodException e) { // ignore } } } catch (Throwable e) { logger.error(e.getMessage(), e); } } } }

这个方法,也比较简单,就是将注解中的各个方法找出来,加上set然后反射执行。

另一个BUG

分析这个方法,还会发现Dubbo中另一个Bug:if (value != null && ! value.equals(method.getDefaultValue())){}只有当注解中的配置项的值与默认值不一致才会生效

比如:启动时检查 按Dubbo的文档中描述:

Dubbo缺省会在启动时检查依赖的服务是否可用,不可用时会抛出异常,阻止Spring初始化完成,以便上线时,能及早发现问题,默认check=true。

但是,com.alibaba.dubbo.config.annotation.Reference#check默认值是false。

2.方案

所以,如果想通过注解完成这个动作,就需要自己动手解决了。 思路有以下几个:

  1. 扩展com.alibaba.dubbo.config.annotation.Reference注解。
  2. 完全使用自定义注解来完成。

在中,曾经分析过,Dubbo的注解完全是由AnnotationBean来完成的。所以,以上两种方式,都是需要修改AnnotationBean的处理逻辑。

com.alibaba.dubbo.config.annotation.Reference并没有加上@Inherited,所以,要想不修改Dubbo源码来扩展Reference是难以操作的。

如果在完全替换,使用自定义注解。那就会牵连着要改动ReferenceBean,ReferenceConfig

所以,只好在保留Reference体系的基础上,叠加上自定义处理过程了。

3.实施

自定义注解:

InterfaceMethods:

package com.roc.dubbo.config.spring.annotation;import java.lang.annotation.ElementType;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;import java.lang.annotation.Target;/** * Created by roc on 2017/3/30. */@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.METHOD})public @interface InterfaceMethods {    Method[] methods() default {};}

Method:

package com.roc.dubbo.config.spring.annotation;import java.lang.annotation.Retention;import java.lang.annotation.RetentionPolicy;/** * Dubbo方法配置 * Created by roc on 2017/3/30. */@Retention(RetentionPolicy.RUNTIME)public @interface Method {    /**     * @return 方法名     */    String name();    /**     * @return 负载均衡策略     */    String loadbalance() default "";    /**     * @return 远程服务调用重试次数     */    int retries() default -1;    /**     * @return 方法使用线程数限制     */    int executes() default 0;    /**     * @return 每服务消费者最大并发调用限制     */    int actives() default 0;    /**     * @return 是否过时     */    boolean deprecated() default false;    /**     * @return 是否需要开启stiky策略     */    boolean sticky() default false;    /**     * @return 是否需要返回     */    boolean isReturn() default true;    /**     * @return 方法调用超时时间(毫秒)     */    int timeout() default -1;}

继上次扩展的com.roc.dubbo.config.spring.AnnotationBean的基础上,添加自定义注解的处理逻辑:

增加:getMethods方法

private com.roc.dubbo.config.spring.annotation.Method[] getMethods(AccessibleObject accessibleObject) {        InterfaceMethods interfaceMethods = accessibleObject.getAnnotation(InterfaceMethods.class);        com.roc.dubbo.config.spring.annotation.Method[] methodArray = null;        if (interfaceMethods != null) {            methodArray = interfaceMethods.methods();        }        return methodArray;    }

修改:refer方法

private Object refer(Reference reference, Class
referenceClass, com.roc.dubbo.config.spring.annotation.Method[] methodArray) { // method.getParameterTypes()[0] ....... String methodKey = "&"; if (methodArray != null) { for (int i = 0; i < methodArray.length; i++) { methodKey += methodArray[i] + "&"; } } String key = reference.group() + "/" + interfaceName + ":" + reference.version() + methodKey; ReferenceBean
referenceConfig = referenceConfigs.get(key); if (referenceConfig == null) { referenceConfig = new ReferenceBean(reference); //修复check默认值的BUG// referenceConfig.setCheck(reference.check()); // >>>>>>>>>>>>>>>>>>>>>>>> if (void.class.equals(reference.interfaceClass()) && "".equals(reference.interfaceName()) && referenceClass.isInterface()) { referenceConfig.setInterface(referenceClass); } if (methodArray != null && methodArray.length > 0) { List
methodConfigList = new ArrayList<>(); for (int i = 0; i < methodArray.length; i++) { com.roc.dubbo.config.spring.annotation.Method m = methodArray[i]; MethodConfig mc = new MethodConfig(); mc.setName(m.name()); mc.setDeprecated(m.deprecated()); mc.setExecutes(m.executes()); if (m.retries() >= 0) { mc.setRetries(m.retries()); } if (StringUtils.isNotEmpty(m.loadbalance())) { mc.setLoadbalance(m.loadbalance()); } if(m.timeout() >= 0){ mc.setTimeout(m.timeout()); } mc.setActives(m.actives()); mc.setSticky(m.sticky()); mc.setReturn(m.isReturn()); methodConfigList.add(mc); } referenceConfig.setMethods(methodConfigList); } ......... } referenceConfigs.putIfAbsent(key, referenceConfig); referenceConfig = referenceConfigs.get(key); } return referenceConfig.get(); }

这样,在使用时需要同时使用Dubbo注解和自定义注解:

@Reference@InterfaceMethods(methods = {@Method(name = "applyPay", retries = 0, timeout = 3000)})    private PayService payCenter;

通过这样改动,即可在不改动Dubbo源码的基础上(不用对Dubbo重编译打包)加上自定义注解处理逻辑。

4 其他解题思路

上面通过自定义注解,并在AnnotationBean中添加额外逻辑来处理。如果仅仅是为了加上方法超时,重试等配置参数,仍然感觉比较繁杂。

其实,Dubbo的服务注册、引用处理最终表现形式就是就是注册中心的URL。 例如:

consumer://127.0.0.1/com.roc.pay.service.PayService?application=egege_web&applyPay.timeout=4000&category=consumers&check=false&default.check=false&default.group=TEST&dubbo=2.5.3&interface=com.roc.pay.service.PayService&methods=applyBatchPay,applyPay&organization=com.egege&owner=egege&pid=1303&revision=0.1.1&side=consumer×tamp=1490867465568

URL中对于applyPay方法的超时配置表现为:applyPay.timeout=4000

这样就说明各种配置,最终都是转换成了URL的参数。

所以,我们其实可以通过Dubbo注解中的parameters属性来直接操作URL:

@Reference(parameters = {"applyPay.timeout","4000"})private PayService payCenter;

这样即可。

转载于:https://my.oschina.net/roccn/blog/871032

你可能感兴趣的文章
记录自写AFNetWorking封装类
查看>>
没想到cnblog也有月经贴,其实C#值不值钱不重要。
查看>>
【转】LUA内存分析
查看>>
springboot使用schedule定时任务
查看>>
[转] Entity Framework Query Samples for PostgreSQL
查看>>
XDUOJ 1115
查看>>
PHP学习(四)---PHP与数据库MySql
查看>>
模版方法模式--实现的capp流程创建与管理
查看>>
软件需求分析的重要性
查看>>
eclipse的scala环境搭建
查看>>
UVA465:Overflow
查看>>
HTML5-placeholder属性
查看>>
Android选择本地图片过大程序停止的经历
查看>>
poj 2187:Beauty Contest(旋转卡壳)
查看>>
《Flask Web开发》里的坑
查看>>
Python-库安装
查看>>
Git笔记
查看>>
普通人如何从平庸到优秀,在到卓越
查看>>
SLAM数据集
查看>>
c#学习笔记05——数组&集合
查看>>