当前位置:   article > 正文

改造Dubbo,使其可以对接口方法进行注解配置

dubbo注解@method不能用于方法了吗

在之前的文章中介绍过了基于Spring 4 + Dubbo 的注解配置。参见《改造Dubbo,使其能够兼容Spring 4注解配置》

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

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

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

1.分析

《改造Dubbo,使其能够兼容Spring 4注解配置》中,曾经分析过,Dubbo的注解完全是由AnnotationBean来完成的。 那先来看看AnnotationBean是怎么处理配置的:

  1. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
  2. ...
  3. Field[] fields = bean.getClass().getDeclaredFields();
  4. for (Field field : fields) {
  5. try {
  6. if (! field.isAccessible()) {
  7. field.setAccessible(true);
  8. }
  9. Reference reference = field.getAnnotation(Reference.class);
  10. if (reference != null) {
  11. Object value = refer(reference, field.getType());
  12. if (value != null) {
  13. field.set(bean, value);
  14. }
  15. }
  16. } catch (Throwable e) {
  17. logger.error("Failed to init remote service reference at filed " + field.getName() + " in class " + bean.getClass().getName() + ", cause: " + e.getMessage(), e);
  18. }
  19. }
  20. ...
  21. }

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

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

那再来看看ReferenceBean

  1. public class ReferenceBean<T>
  2. extends ReferenceConfig<T>
  3. implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean

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

  1. public class ReferenceConfig<T> extends AbstractReferenceConfig {
  2. ...
  3. // 方法配置
  4. private List<MethodConfig> methods;
  5. ...
  6. }

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

  1. private Object refer(Reference reference, Class<?> referenceClass) { //method.getParameterTypes()[0]
  2. ...
  3. String key = reference.group() + "/" + interfaceName + ":" + reference.version();
  4. ReferenceBean<?> referenceConfig = referenceConfigs.get(key);
  5. if (referenceConfig == null) {
  6. referenceConfig = new ReferenceBean<Object>(reference);
  7. ...
  8. }
  9. referenceConfigs.putIfAbsent(key, referenceConfig);
  10. referenceConfig = referenceConfigs.get(key);
  11. }
  12. return referenceConfig.get();
  13. }

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

  1. protected void appendAnnotation(Class<?> annotationClass, Object annotation) {
  2. Method[] methods = annotationClass.getMethods();
  3. for (Method method : methods) {
  4. if (method.getDeclaringClass() != Object.class
  5. && method.getReturnType() != void.class
  6. && method.getParameterTypes().length == 0
  7. && Modifier.isPublic(method.getModifiers())
  8. && ! Modifier.isStatic(method.getModifiers())) {
  9. try {
  10. String property = method.getName();
  11. if ("interfaceClass".equals(property) || "interfaceName".equals(property)) {
  12. property = "interface";
  13. }
  14. String setter = "set" + property.substring(0, 1).toUpperCase() + property.substring(1);
  15. Object value = method.invoke(annotation, new Object[0]);
  16. if (value != null && ! value.equals(method.getDefaultValue())) {
  17. Class<?> parameterType = ReflectUtils.getBoxedClass(method.getReturnType());
  18. if ("filter".equals(property) || "listener".equals(property)) {
  19. parameterType = String.class;
  20. value = StringUtils.join((String[]) value, ",");
  21. } else if ("parameters".equals(property)) {
  22. parameterType = Map.class;
  23. value = CollectionUtils.toStringMap((String[]) value);
  24. }
  25. try {
  26. Method setterMethod = getClass().getMethod(setter, new Class<?>[] { parameterType });
  27. setterMethod.invoke(this, new Object[] { value });
  28. } catch (NoSuchMethodException e) {
  29. // ignore
  30. }
  31. }
  32. } catch (Throwable e) {
  33. logger.error(e.getMessage(), e);
  34. }
  35. }
  36. }
  37. }

这个方法,也比较简单,就是将注解中的各个方法找出来,加上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,使其能够兼容Spring 4注解配置》中,曾经分析过,Dubbo的注解完全是由AnnotationBean来完成的。所以,以上两种方式,都是需要修改AnnotationBean的处理逻辑。

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

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

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

3.实施

自定义注解:

InterfaceMethods:

  1. package com.roc.dubbo.config.spring.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. /**
  7. * Created by roc on 2017/3/30.
  8. */
  9. @Retention(RetentionPolicy.RUNTIME)
  10. @Target({ElementType.FIELD, ElementType.METHOD})
  11. public @interface InterfaceMethods {
  12. Method[] methods() default {};
  13. }

Method:

  1. package com.roc.dubbo.config.spring.annotation;
  2. import java.lang.annotation.Retention;
  3. import java.lang.annotation.RetentionPolicy;
  4. /**
  5. * Dubbo方法配置
  6. * Created by roc on 2017/3/30.
  7. */
  8. @Retention(RetentionPolicy.RUNTIME)
  9. public @interface Method {
  10. /**
  11. * @return 方法名
  12. */
  13. String name();
  14. /**
  15. * @return 负载均衡策略
  16. */
  17. String loadbalance() default "";
  18. /**
  19. * @return 远程服务调用重试次数
  20. */
  21. int retries() default -1;
  22. /**
  23. * @return 方法使用线程数限制
  24. */
  25. int executes() default 0;
  26. /**
  27. * @return 每服务消费者最大并发调用限制
  28. */
  29. int actives() default 0;
  30. /**
  31. * @return 是否过时
  32. */
  33. boolean deprecated() default false;
  34. /**
  35. * @return 是否需要开启stiky策略
  36. */
  37. boolean sticky() default false;
  38. /**
  39. * @return 是否需要返回
  40. */
  41. boolean isReturn() default true;
  42. /**
  43. * @return 方法调用超时时间(毫秒)
  44. */
  45. int timeout() default -1;
  46. }

继上次《改造Dubbo,使其能够兼容Spring 4注解配置》扩展的com.roc.dubbo.config.spring.AnnotationBean的基础上,添加自定义注解的处理逻辑:

增加:getMethods方法

  1. private com.roc.dubbo.config.spring.annotation.Method[] getMethods(AccessibleObject accessibleObject) {
  2. InterfaceMethods interfaceMethods = accessibleObject.getAnnotation(InterfaceMethods.class);
  3. com.roc.dubbo.config.spring.annotation.Method[] methodArray = null;
  4. if (interfaceMethods != null) {
  5. methodArray = interfaceMethods.methods();
  6. }
  7. return methodArray;
  8. }

修改:refer方法

  1. private Object refer(Reference reference, Class<?> referenceClass, com.roc.dubbo.config.spring.annotation.Method[] methodArray) { // method.getParameterTypes()[0]
  2. .......
  3. String methodKey = "&";
  4. if (methodArray != null) {
  5. for (int i = 0; i < methodArray.length; i++) {
  6. methodKey += methodArray[i] + "&";
  7. }
  8. }
  9. String key = reference.group() + "/" + interfaceName + ":" + reference.version() + methodKey;
  10. ReferenceBean<?> referenceConfig = referenceConfigs.get(key);
  11. if (referenceConfig == null) {
  12. referenceConfig = new ReferenceBean<Object>(reference);
  13. //修复check默认值的BUG
  14. // referenceConfig.setCheck(reference.check());
  15. // >>>>>>>>>>>>>>>>>>>>>>>>
  16. if (void.class.equals(reference.interfaceClass()) && "".equals(reference.interfaceName())
  17. && referenceClass.isInterface()) {
  18. referenceConfig.setInterface(referenceClass);
  19. }
  20. if (methodArray != null && methodArray.length > 0) {
  21. List<MethodConfig> methodConfigList = new ArrayList<>();
  22. for (int i = 0; i < methodArray.length; i++) {
  23. com.roc.dubbo.config.spring.annotation.Method m = methodArray[i];
  24. MethodConfig mc = new MethodConfig();
  25. mc.setName(m.name());
  26. mc.setDeprecated(m.deprecated());
  27. mc.setExecutes(m.executes());
  28. if (m.retries() >= 0) {
  29. mc.setRetries(m.retries());
  30. }
  31. if (StringUtils.isNotEmpty(m.loadbalance())) {
  32. mc.setLoadbalance(m.loadbalance());
  33. }
  34. if(m.timeout() >= 0){
  35. mc.setTimeout(m.timeout());
  36. }
  37. mc.setActives(m.actives());
  38. mc.setSticky(m.sticky());
  39. mc.setReturn(m.isReturn());
  40. methodConfigList.add(mc);
  41. }
  42. referenceConfig.setMethods(methodConfigList);
  43. }
  44. .........
  45. }
  46. referenceConfigs.putIfAbsent(key, referenceConfig);
  47. referenceConfig = referenceConfigs.get(key);
  48. }
  49. return referenceConfig.get();
  50. }

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

  1. @Reference
  2. @InterfaceMethods(methods = {@Method(name = "applyPay", retries = 0, timeout = 3000)})
  3. 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:

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

这样即可。

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/165868
推荐阅读
相关标签
  

闽ICP备14008679号