【dubbo学习系列】dubbo消费端的代理生成详解(@DubboReference和@Reference)

2023-10-30

spring中dubbo实现RPC

在使用dubbo的过程中,我们的用法是在Service中引入 远程接口,然后标记上注解写入名称与版本号等一系列配置信息,然后在需要进行RPC通信的地方调用接口方法,然后就能获取到信息了。
那么按照这种思路,就应该有下列几个步骤需要完成

  1. 发现使用dubbo注解的成员与方法并托管于spring容器
  2. 在对象实例中找到有指定注解的成员变量或方法
  3. 通过动态代理的方式,编织通信代码到访问逻辑中

当然,上述是主要脉络,细分的话还有 序列化问题,地址的负载均衡问题,访问策略问题等等,本文主要讲述的就是上面的主要脉络。

这里主要是对@Reference和@DubboReference不同点做分析

更好的表述其实应该是新老版本的区别,这里@Reference就代指于旧版本2.7.7以下,@DubboReference就代指新版本2.7.7以上。后文也以@Reference和@DubboReference区分新旧

如何加载@Reference和@DubboReference注解

注意,@Reference已被废弃,2.7.7后使用@DubboReference

在springboot中我们会在启动类上添加 @EnableDubbo这个注解,而这个注解中有两个功能性注解 @EnableDubboConfig
@DubboComponentScan
前者负责dubbo配置的初始化,后者负责扫描dubbo配置中指定的包范围,也就是DubboComponentScanRegistrar.class类的职责

@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
}
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
}

DubboComponentScanRegistrar

在源码中,作者标注出了这个类需要关注的几个关键类和注解

/**
 * Dubbo {@link DubboComponentScan} Bean Registrar
 *
 * @see Service  注解,老版本dubbo标注的服务方Service注解
 * @see DubboComponentScan  注解,用于指定dubbo的管理范围,也就是扫描范围
 * @see ImportBeanDefinitionRegistrar  自身实现的接口,在spring的bean定义注册时期进行处理逻辑
 * @see ServiceAnnotationPostProcessor 对扫描路径的处理
 * @see ReferenceAnnotationBeanPostProcessor 处理dubbo注解,并生成代理类,【主要关注点】
 * @since 2.5.7
 */
 public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
 }

主要关注registerBeanDefinitions方法

    @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

       // initialize dubbo beans
       //初始化信息
       DubboSpringInitializer.initialize(registry);
       //获取需要扫描的包路径,这个方法主要关注点就是,包路径的优先级关系与默认值问题
       Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
       //为后续扫描上面获取的包路径做准备
       registerServiceAnnotationPostProcessor(packagesToScan, registry);
   }

先来看看第二步包路径优先级问题 ,这个问题很简单

  • 先看@DubboComponentScan
  • 在看@EnableDubbo
  • 最后如果都没有,则选用启动类的包路径

所以一般我们不设置包路径就是因为默认会扫描 启动类的包路径,也就会查询我们项目下面所有的类

   private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
       // get from @DubboComponentScan
       Set<String> packagesToScan = getPackagesToScan0(metadata, DubboComponentScan.class, "basePackages", "basePackageClasses");

       // get from @EnableDubbo, compatible with spring 3.x
       if (packagesToScan.isEmpty()) {
           packagesToScan = getPackagesToScan0(metadata, EnableDubbo.class, "scanBasePackages", "scanBasePackageClasses");
       }

       if (packagesToScan.isEmpty()) {
           return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
       }
       return packagesToScan;
   }

第三步,就是创建ServiceAnnotationPostProcessor类的bean定义,然后将其注册到spring中

    private void registerServiceAnnotationPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {

        //针对ServiceAnnotationPostProcessor类,创建bean定义
        BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationPostProcessor.class);
        //添加构造参数为包路径
        builder.addConstructorArgValue(packagesToScan);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
    }

包路径的扫描,过滤等处理就交由ServiceAnnotationPostProcessor来向下进行,后续就不在展开讲解该类了,有兴趣的可以自行查看。

概括

DubboComponentScanRegistrar类主要目的就是,将托管范围内的类,并入spring中。

发现@Refrence和@DubboReference注解,并编织RPC通信逻辑

上述流程完成了第一步,将dubbo的相关注解纳入spring的管理,下一步就是借助spring来发现相关注解并进行一些逻辑的编织生成代理对象。

ReferenceAnnotationBeanPostProcessor(核心)

该类是重点类,是dubbo逻辑编织处理类,这里要说明下,@Reference和@DubboReference的处理是有很大区别的(新老版本),背后的思想方式很有借鉴性。

/**
* 该类继承AbstractAnnotationBeanPostProcessor,核心逻辑分为两步
* 第一,发现被dubbo注解标识的成员变量,方法,或者类
* 第二,生成动态代理类,注入成员变量或方法,或者生成类(Bean)放入spring容器
**/
public class ReferenceAnnotationBeanPostProcessor extends AbstractAnnotationBeanPostProcessor{
}
@Reference

这里 @Reference 发现主要借助于,postProcessMergedBeanDefinition方法在Bean创建之前合并定义的时候进行判断该类中是否有指定注解,如果有则持有其反射对象,待后面通过 postProcessPropertyValues 统一处理 (这种方式,在spring中与使用@Value注解流程一致)。

下面是第一步 postProcessMergedBeanDefinition的处理流程:

    @Override
    public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
        if (beanType != null) {
            if (isReferenceBean(beanDefinition)) {
            ...
            } else if (isAnnotatedReferenceBean(beanDefinition)) {
            ...
            } else {
                //上面几个判断主要是对于@DubboReference的处理
                //第一步,找到当前类中使用@Reference的成员变量和方法
                AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
                metadata.checkConfigMembers(beanDefinition);
                try {
                    //第二步,放入容器中
                    prepareInjection(metadata);
                } catch (Exception e) {
                    throw new IllegalStateException("Prepare dubbo reference injection element failed", e);
                }
            }
        }
    }

而容器就是该类上面的两个成员变量

	//管理成员变量
    private final ConcurrentMap<InjectionMetadata.InjectedElement, String> injectedFieldReferenceBeanCache =
            new ConcurrentHashMap<>(CACHE_SIZE);
	//管理方法
    private final ConcurrentMap<InjectionMetadata.InjectedElement, String> injectedMethodReferenceBeanCache =
            new ConcurrentHashMap<>(CACHE_SIZE);

这里有个注意点,对于静态成员变量dubbo是不会进行处理的

    private List<AbstractAnnotationBeanPostProcessor.AnnotatedFieldElement> findFieldAnnotationMetadata(final Class<?> beanClass) {
		...
		 if (Modifier.isStatic(field.getModifiers())) {
			 if (logger.isWarnEnabled()) {
				logger.warn("@" + annotationType.getName() + " is not supported on static fields: " + field);
			}
		 	return;
		 }
		...
    }

如果需要设置为静态成员变量,只能通过访问方法,然后在其中设置,注意:这种使用方式,将会使你RPC通信内部的信息共享,导致不可预见的问题,请避免使用。

到这里,需要进行反射持有的对象已经放入容器中了,下一步就行进行代理生成并注入 。

下面是 postProcessPropertyValues的处理流程 , 也是两步
第一步,从容器中获取反射对象,如果没有,则重复发现步骤
第二步,就是进行动态代理,并注入

    @Override
    public PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

        try {
            //第一步,从容器中获取反射对象,如果没有,则重复发现步骤
            AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
            prepareInjection(metadata);
            //第二步进行注入
            metadata.inject(bean, beanName, pvs);
        } catch (BeansException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
                    + " dependencies is failed", ex);
        }
        return pvs;
    }

而第二步方法主要是调用实现父类的doGetInjectedBean方法,注意:下面是旧版本中的代码

    @Override
    protected Object doGetInjectedBean(Reference reference, Object bean, String beanName, Class<?> injectedType,
                                       InjectionMetadata.InjectedElement injectedElement) throws Exception {

        String referencedBeanName = buildReferencedBeanName(reference, injectedType);
		//生成ReferenceBean,这个bean是一个FactoryBean,这里并没有将其托管与Spring容器
        ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referencedBeanName, reference, injectedType, getClassLoader());

        cacheInjectedReferenceBean(referenceBean, injectedElement);
		//生成代理类
        Object proxy = buildProxy(referencedBeanName, referenceBean, injectedType);
		//返回该对象
        return proxy;
    }

到此,注解对象就注入完毕了。

@DubboReference

@DubboReference 的发现则需要借助于spring容器,通过手动创建ReferenceBean托管于Spring容器,后续通过spring的 @Autowired 注解获取实例 详细请看@DubboReference的注释

在DubboReference中呢,也兼容了老版本的注解 ,doGetInjectedBean 变更为了直接从BeanFactory中获取

    @Override
    protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
                                       AnnotatedInjectElement injectedElement) throws Exception {

        if (injectedElement.injectedObject == null) {
            throw new IllegalStateException("The AnnotatedInjectElement of @DubboReference should be inited before injection");
        }

        return getBeanFactory().getBean((String) injectedElement.injectedObject);
    }

在老注解@Reference不变的情况下,只需要自己的Configuration类中注入对应的ReferenceBean就OK了。

@Reference和@DubboReference的区别

这里其实已经可以看出明显思路的变化了,@Reference设计上是游离在Spring容器之外的,直接通过反射的方式进行增强,不依托于spring的容器,而 @DubboReference 则通过将FactoryBean融入到Spring环境中,真正的像使用本地方法一样,进行远程访问。

@DubboReference的使用方式

参考@DubboReference注解上面的注释就好了。

* Step 1: Register ReferenceBean in Java-config class:
 * <pre class="code">
 * @Configuration
 * public class ReferenceConfiguration {
 *     @Bean
 *     @DubboReference(group = "demo")
 *     public ReferenceBean<HelloService> helloService() {
 *         return new ReferenceBean();
 *     }
 *
 *     @Bean
 *     @DubboReference(group = "demo", interfaceClass = HelloService.class)
 *     public ReferenceBean<GenericService> genericHelloService() {
 *         return new ReferenceBean();
 *     }
 * }
 * </pre>
 *
 * Step 2: Inject ReferenceBean by @Autowired
 * <pre class="code">
 * public class FooController {
 *     @Autowired
 *     private HelloService helloService;
 *
 *     @Autowired
 *     private GenericService genericHelloService;
 * }
 * </pre>

总结

依托于Spring容器的好处在于职责的划分更加清晰,dubbo本身只负责于rpc的通信,而bean的管理交还给spring,而且使ReferenceAnnotationBeanPostProcessor类更加轻,dubbo的关注点更加集中。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【dubbo学习系列】dubbo消费端的代理生成详解(@DubboReference和@Reference) 的相关文章

  • Java - 将无符号十六进制字符串解析为有符号长整型

    我有一堆十六进制字符串 其中之一是 d1bc4f7154ac9edb 这是 3333702275990511909 的十六进制值 如果执行 Long toHexString d1bc4f7154ac9edb 这与您得到的十六进制相同 现在
  • 将 WAR 部署到 Tomcat(Spring Boot + Angular)

    我正在尝试使用以下命令部署 Spring Boot 应用程序WAR包装至Tomcat 10 应用程序已成功部署 但是 当我尝试访问端点时 它会导致404 未找到 战争文件 应用程序 war http localhost 8080 appli
  • IBM Websphere MQ - 用于 Tomcat 部署的 EJB 和 MDB 迁移

    我已经为此苦苦挣扎了很长一段时间 我有一个 IBM Websphere MQ 它使用 EJB 和 MDB 以下是配置ejb mdb的地方
  • 在java代码中创建postgresql表

    我有一个与 postgreSQL 数据库连接的 java 代码 现在 我希望当它连接到数据库时 我还将创建数据库表 但我的问题是 它不会创建数据库 我不知道问题是什么 这是我的代码 Statement st null ResultSet r
  • openFileOutput 在单例类中无法正常工作 - 想法/解决方法?

    作为一名 Android 开发新手 我遇到了一些奇怪的问题 我想创建一个类 它方法其他类 活动 任何可以用于以某种特殊方式处理文件的类 假设为了简单起见 我们将记录一些内容 如果我在活动中执行以下操作 例如在 OnClick 侦听器中 则一
  • 二元运算符 >=、-、* 的错误操作数类型

    我无法弄清楚如何修复代码中不断出现的这些错误 import java util Scanner public class Unit02Prog1 public static void main String args Scanner inp
  • Android Studio 与 Google Play 服务的编译问题

    我正在运行 Android Studio 0 8 4 并在 Android Studio 0 8 2 上尝试过此操作 我正在运行 Java JDK 1 8 0 11 并尝试使用 JDK 1 8 0 05 每当我尝试构建我的 android
  • 使用 Spring MVC 在 jar 文件中显示 jsp 页面

    我正在使用 Spring MVC 3 2 2 在 java 中开发一个 Web 应用程序 我在从 jar 文件中加载 jsp 页面时遇到问题 Spring MVC Web应用程序具有以下结构 META INF WEB INF spring
  • 无法在 Intellij 中运行主类[重复]

    这个问题在这里已经有答案了 我有以下项目结构 ProjectRoot src Main examples libs My src文件夹被标记为sources在 Intellij 中 现在 当我想运行 Main 类时 出现以下错误 Excep
  • 递归取消 allOf CompletableFuture

    如果我有 CompletableFuture
  • 生成一定长度的所有排列

    假设我们有一个字母表 abcdefghiklimnop 如何以有效的方式以五个一组的形式重复该字母表来递归生成排列 几天来我一直在为此苦苦挣扎 任何反馈都会有帮助 本质上这与 生成给定字符串的所有排列 https stackoverflow
  • JFrame 在连续运行代码时冻结

    我在使用时遇到问题JFrame 它会冻结 连续运行代码 下面是我的代码 点击时btnRun 我调用了该函数MainLoop ActionListener btnRun Click new ActionListener Override pu
  • 始终将双精度舍入

    我怎么总是能把一个double to an int 并且永远不要将其四舍五入 我知道Math round double 但我希望它始终向上舍入 所以如果是的话3 2 四舍五入为 4 您可以使用Math ceil method 请参阅Java
  • javadoc 子集/java 库组织

    我自己从来没有运行过javadoc 无论是在命令行还是ant 的 javadoc 任务 http ant apache org manual Tasks javadoc html 我将使用 ant 我需要为我编写的库生成 javadoc 问
  • 获取接收者的设备令牌以在 Firebase 中发送通知

    所以我正在学习如何使用 firebase 发送设备到设备的通知 我看到了这个answer https stackoverflow com a 42548586 5237289发送通知 看起来很简单 现在 我知道要获取发件人的令牌 它应该如下
  • 添加 char 和 int

    据我了解 字符是一个字符 即一个字母 一个digit 标点符号 制表符 空格或类似的东西 因此 当我这样做时 char c 1 System out println c 输出 1 正是我所期望的 那么为什么当我这样做时 int a 1 ch
  • 难以理解 通配符

    我有一个非常基本的问题 下面的代码无法编译 假设 Apple Extends Fruit List
  • 如何更改 JAX-WS Web 服务的地址位置

    我们目前已经公开了具有以下 URL 的 JAX RPC Web 服务 http xx xx xx xx myservice MYGatewaySoapHttpPort wsdl http xx xx xx xx myservice MYGa
  • FetchType.LAZY 不适用于休眠中的 @ManyToOne 映射

    简而言之 我的 Child 类与 Parent 类之间存在多对一的关系 我想加载所有的孩子 而不必加载他们的父母详细信息 我的孩子班级是 Entity public class Child implements Serializable I
  • Graphics2D setfont() 严重减慢了 java 应用程序的启动速度

    我正在用java制作一个游戏 它每秒刷新60次 每次执行循环时 我都会使用 g2d 来绘制图像和字符串 如果我这样做的话一切都会很好g2d setFont new Font Arial Font PLAIN 8 和抽绳 这将是正常的 但如果

随机推荐

  • CMake简介,打包so文件,编译实际项目

    CMake简介和使用示例 CMake是常用的跨平台编译器 图像这块在给服务端做开发时 常有两个需求 1 代码打成 so包 供别人调用 2 编译 测试 用valgrind测内存情况 工程较大时 借助CMake完成很方便 下面分别给出两种情况下
  • 含Java岗988道题分享 备战金九银十,你准备好了吗?,阿里,腾讯秋招面试题解析。

    在前段时间里公司的项目基本都很闲 很多人觉得工作起来没意思相继走了 而我考虑到自己的发展 并没有裸辞 而是一边上班 另一边在面试 从3月底开始面试 面到5月底 三十家公司 因为疫情原因有些面试是远程面试 我从不打没准备的仗 我是一个喜欢总结
  • chown 命令

    NAME chown change file owner and group SYNOPSIS chown OPTION OWNER GROUP FILE chown OPTION reference RFILE FILE 当使用 refe
  • 数据库-面试题(持续更新)

    来自牛客网的汇总 1 MySQL查询时 只有满足联接条件的记录才包含在查询结果 这种联接是 内联接 内联接 典型的联接运算 使用像 或 lt gt 之类的比较运算符 包括相等联接和自然联接 内联接使用比较运算符根据每一表共有的列的值匹配两个
  • 在simulink中查看bode图

    打开simulink 在library里面找到inport和outport 然后在inport和outport之间使用传递函数 想要查看bode图的传函 连接 按如下路径点击Analysis Control Design Linear An
  • 【2.学习__签名证书和加密证书】

    实习期学习一些签名和加密的知识 暂时先这样 有时间了再整理 学习的方法 先学习证书文件内容 结构 再针对问题进行学习 证书相关的知识 1 证书的结构大致是什么样的 证书的机构分为三部分 tbsCertificate 包含 主题 和 发行者的
  • 【C语言学习笔记】再次深入理解递归——总结设计易错点

    写在前面 其实我也说不太清楚到底递归算不算算法 因为我一开始从0基础接触递归是从 算法图解 这本书中得知的 也很推荐刚学算法的朋友可以先看看这本书 写的挺不错的 也就把它当成算法了 但写了那么多题目 渐渐的感觉递归这个东西把 它更像是一种工
  • sqli-labs第十三和十四关(post请求-报错盲注)

    一上来 先加个单引号探探路 有sql报错回显 在单引号后继续加上and 1 试图让sql报错显示更多信息 从near and password 1 LIMIT 0 1 at line 1可以判断 这关就用单引号加括号了 没什么意思 注入 却
  • 深度学习:经典卷积神经网络和目标检测网络

    最近自己会把自己个人博客中的文章陆陆续续的复制到CSDN上来 欢迎大家关注我的 个人博客 以及我的github 本文主要讲解关于有关物体检测的相关网络 具体包括R CNN Fast R CNN Faster R CNN和Mask R CNN
  • 流量分析例题

    流量分析 一 题目背景 某公司内网网络被黑客渗透 简单了解 黑客首先攻击了一台web服务器 破解了后台的账户密码 随之利用破解的账号密码登陆了mail系统 然后获取了vpn的申请方式 然后登陆了vpn 在内网pwn掉了一台打印机 请根据提供
  • 第1期技术: DQN算法原理及实现过程

    深度强化学习实验室 DeepRLhub 访问官网 http deeprlhub com 特别声明 本文是作者在充分知晓著作权细则的情况下 经过个人付出或者翻译他人著作内容 并已注明翻译原文来源的情况下授权给 深度强化学习实验室 CSDN博客
  • Java 国际化简单使用

    文章目录 Java国际化使用 Locale 本地化工具类 配置文件本地化 ResourceBundle Java国际化使用 Locale 是表示语言和国际的类 是国际化应用的基础 语言完整版 http www loc gov standar
  • python使用ctypes调用dll

    因为 ctypes 是内置模块 可以直接使用 from ctypes import 加载dll程序 from ctypes import dll CDLL test sdk dll 调用dll方法 直接调用 from ctypes impo
  • C++----类型转换

    C 类型转换的方式 1 static cast static cast用于非多态类型的转换 静态转换 编译器隐式执行的任何类型转换都可用 static cast 但它不能用于两个不相关的类型进行转换 2 reinterpret cast r
  • OC中一些容易混淆和忘记的知识点总结

    1 三目运算符 lt 表达式1 gt lt 表达式2 gt lt 表达式3 gt 运算符的含义是 先求表达式1的值 如果为真 则执行表达式2 并返回表达式2的结果 如果表达式1的值为假 则执行表达式3 并返回表达式3的结果 可以理解为条件
  • Self-study Python Fish-C Note-3 P15-P19

    分支和循环 branch and loop python中分支和循环结构语法的注意和特点 1 严格的缩进 2 if elif else while for后面要加冒号 也只有冒号一种标点符号 A 分支 branch a 语法结构 1 判断一
  • 华为OD机试 - 服务失效判断(Java)

    题目描述 某系统中有众多服务 每个服务用字符串 只包含字母和数字 长度 lt 10 唯一标识 服务间可能有依赖关系 如A依赖B 则当B故障时导致A也故障 依赖具有传递性 如A依赖B B依赖C 当C故障时导致B故障 也导致A故障 给出所有依赖
  • dynamic 365 前端操作

    dynamic 365 常用js操作和方法 dynamic 365 常用js dynamic 365 常用js 获取当前用户id Xrm Page context getUserId 获取当前用户的用户名 Xrm Page context
  • Conference and Journal Level in 2016

    中国计算机学会推荐国际学术会议和期刊目录 2015 年 中国计算机学会 中国计算机学会推荐国际学术期刊 计算机体系结构 并行与分布计算 存储系统 一 A 类 序号 刊物简称 刊物全称 出版社 网址 1 TOCS ACM Transactio
  • 【dubbo学习系列】dubbo消费端的代理生成详解(@DubboReference和@Reference)

    文章目录 spring中dubbo实现RPC 如何加载 Reference和 DubboReference注解 DubboComponentScanRegistrar 概括 发现 Refrence和 DubboReference注解 并编织