辅助链接

Dubbo系列之 (一)SPI扩展

Dubbo系列之 (二)Registry注册中心-注册(1)

Dubbo系列之 (三)Registry注册中心-注册(2)

Dubbo系列之 (四)服务订阅(1)

介绍

dubbo的服务订阅可以通过2种方式: 1)通过xml文件的标签<dubbo:reference /> ;2)通过注解@DubboReference。

这2种服务订阅在使用上基本没区别,因为标签<dubbo:reference />上的属性字段都可以在注解@DubboReference上对应的找到。一般使用XML的配置文件方式来订阅服务。

但是这2种的源码实现上有一定的区别和公用。

首先,这2种的实现最终都是调用ReferenceBean#get()方法来产生代理对象和订阅服务。所以ReferenceBean#get()方法是我们分析的重点。

其次,标签<dubbo:reference />的实现方式是通过Spring自定义的标签来实现的,当一个<dubbo:reference />标签被DubboBeanDefinitionParser处理转化后,会变成一个RootBeanDefinition,接着注册到Spring容器中。而这个RootBeanDefinition对应的类就是ReferenceBean,这个ReferenceBean 实现了工厂Bean接口FactoryBean,所以在具体创建这个Bean得对象时候,会调用FactoryBean#getObject()来返回具体类对象。刚好这个ReferenceBean的getObject()方法就是调用ReferenceBean#get()来返回具体引用服务类型对象和订阅服务。

再次,注解@DubboReference的实现方式则有所不同,它是通过BeanPostProcessor来实现的。一般我们把注解@DubboReference打在某个被Spring托管的类的成员变量上,例如:

@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService { @DubboReference(check = false, mock = "true" ,version = "2.0.0")
private DemoService demoService;
......
}

所以可以知道@DubboReference注解和@Resource,@Autowired等注入注解类似,都是注入一个DemoService这种类型的对象。

而Dubbo框架正是通过自己编写类似于AutowiredAnnotationBeanPostProcessor的处理器ReferenceAnnotationBeanPostProcessor,通过这个处理器来处理@DubboReference注解。读者可以2者结合起来看。

ReferenceAnnotationBeanPostProcessor 分析

通过xml配置的方式的解析没有太多的价值,所以我们直接分析注解的方式。接下来我们分析Spring扩展点和dubbo框架的相结合内容。

1、Spring相关:一个Bean属性依赖注入的扩展点在哪

在远程服务引用的解析前,我们需要先了解下Spring的一个特殊的BeanPostProcessor,即

InstantiationAwareBeanPostProcessor,该后置处理器主要有三个作用:

1)、Bean实例化前的一个回调:postProcessBeforeInstantiation。

2)、Bean实例化后属性显式填充和自动注入前的回调:postProcessAfterInstantiation

3)、给Bean对象属性自动注入的机会:postProcessPropertyValues

显而易见,被打上@DubboReference 注解的属性值注入,就是利用了这个后置处理器的第三个作用(给Bean对象属性自动注入的机会postProcessPropertyValues)。就是在这个方法内处理Bean对象属性成员的注入。

有了以上Spring扩展点的基础,我们来看下AbstractAnnotationBeanPostProcessor的postProcessPropertyValues。

因为ReferenceAnnotationBeanPostProcessor继承了AbstractAnnotationBeanPostProcessor.

 public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeanCreationException { /**
* 查找bean 下需要注入的相关成员(包括成员变量和方法,即被@DubboReference标注的成员,并把这些这些属性集合封装为一个对象InjectionMetadata,)
* InjectionMetadata 对象内部for 循环,一次注入相关的属性值。
*/
InjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
} catch (BeanCreationException ex) {
throw ex;
} catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of @" + getAnnotationType().getSimpleName()
+ " dependencies is failed", ex);
}
return pvs;
}

这个方法的实现和上面的基础铺垫内容一致。首先第一步就是通过findInjectionMetadata()得到查找需要注入的相关成员。接着通过Spring内置的对象InjectionMetadata来注入bean的相关属性。这里需要注意点,metdata对象封装了该bean 需要注入的所有属性成员,在inject内部for循环一致注入。查看代码类似如下:

public void inject(Object target, String beanName, PropertyValues pvs){
Collection<InjectedElement> elementsToIterate =
(this.checkedElements != null ? this.checkedElements : this.injectedElements);
if (!elementsToIterate.isEmpty()) {
for (InjectedElement element : elementsToIterate) {
element.inject(target, beanName, pvs);
}
}
}

2、Spring相关:一个引用了远程服务的Bean,是如何找到打上@DubboReference注解的成员

通过第一个问题,我们知道当一个Spring Service的对象引用了远程对象(通过注解@DubboReference),是通过postProcessPropertyValues注入的。接着我们需要找到它是如何需要注入的属性成员。

/**
* 找到相关的需要注入的成员元信息,并封装为InjectionMetadata
*
* @param beanName 当前需要被注入的 beanName
* @param clazz 当前需要被注入的 类的Class
* @param pvs 当前 需要被注入bean 的参数
* @return
*/
private InjectionMetadata findInjectionMetadata(String beanName, Class<?> clazz, PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
//获取缓存key,默认为beanName,否则为className
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
//从缓存中拿,如果未null 或者注入属性元数据所在的目标类和当前需要注入属性的bean的类型不一致时,需要重写获取
AnnotatedInjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey); //双层判断锁定
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) { //原来的目标类不一致,先clear下参数属性,但排除需要的参数pvs
metadata.clear(pvs);
}
try {
metadata = buildAnnotatedMetadata(clazz); //通过需要注入的类的字节码clazz,得到需要被注入的属性的元信息。
this.injectionMetadataCache.put(cacheKey, metadata); //放入缓存。
} catch (NoClassDefFoundError err) {
throw new IllegalStateException("Failed to introspect object class [" + clazz.getName() +
"] for annotation metadata: could not find class that it depends on", err);
}
}
}
}
return metadata;
}

该注释已经非常清楚了,其实就是通过buildAnnotatedMetadata()方法构建注入的元数据信息,然后放入缓存injectionMetadataCache中。而buildAnnotatedMetadata()方法的如下:

private AnnotatedInjectionMetadata buildAnnotatedMetadata(final Class<?> beanClass) {
/**
*
* 1、查找 需要注入的成员的元信息
*/
Collection<AnnotatedFieldElement> fieldElements = findFieldAnnotationMetadata(beanClass);
/**
*
* 2、查找 需要注入的方法的元信息
*/
Collection<AnnotatedMethodElement> methodElements = findAnnotatedMethodMetadata(beanClass); /**
*
* 组合返回元信息
*/
return new AnnotatedInjectionMetadata(beanClass, fieldElements, methodElements);
}

其中findFieldAnnotationMetadata()和findAnnotatedMethodMetadata()方法其实就是分别在clazz上的成员和方法上找是否有被@DubboReference打标的属性。

3、真正的属性注入是怎么实现的

Dubbo框架为了实现属性的注入,分别定义了2个注入类,一个AnnotatedFieldElement和一个AnnotatedMethodElement。很显然,一个是整对成员,一个整对方法。这个2个类都继承了

Spring的InjectionMetadata.InjectedElement,然后实现inject方法。接着我们来看下

这2个类的inject 是如何实现:

public class AnnotatedFieldElement extends InjectionMetadata.InjectedElement {

        private final Field field;

        private final AnnotationAttributes attributes;

        private volatile Object bean;

        protected AnnotatedFieldElement(Field field, AnnotationAttributes attributes) {
super(field, null);
this.field = field;
this.attributes = attributes;
} @Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class<?> injectedType = field.getType(); Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this); ReflectionUtils.makeAccessible(field); field.set(bean, injectedObject); } }
private class AnnotatedMethodElement extends InjectionMetadata.InjectedElement {

        private final Method method;

        private final AnnotationAttributes attributes;

        private volatile Object object;

        protected AnnotatedMethodElement(Method method, PropertyDescriptor pd, AnnotationAttributes attributes) {
super(method, pd);
this.method = method;
this.attributes = attributes;
} @Override
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable { Class<?> injectedType = pd.getPropertyType(); Object injectedObject = getInjectedObject(attributes, bean, beanName, injectedType, this); ReflectionUtils.makeAccessible(method); method.invoke(bean, injectedObject); } }

其实就是通过反射,把需要的属性对象注入进来。成员属性就通过field.set()。而方法就通过method.invoke().那么注入的injectedObject对象是如何获取的呢。从上面的inject()方法知道,通过调用getInjectedObject()方法来实现,而该方法其实只是从缓存injectedObjectsCache中获取注入的对象,如果不存在,就调用doGetInjectedBean().接着放入缓存中。

doGetInjectedBean()方法的作用见注释:

 /**
*
*
* 该方法是个模板方法,用来得到一个需要注入类型的的对象。
*
* @param attributes ReferenceBean注解属性
* @param bean 需要被注入的对象,一般是Spring Bean
* @param beanName 需要被注入的对象名
* @param injectedType 注入对象的类型
* @param injectedElement 注入对象描述元信息
* @return
* @throws Exception
*/ @Override
protected Object doGetInjectedBean(AnnotationAttributes attributes, Object bean, String beanName, Class<?> injectedType,
InjectionMetadata.InjectedElement injectedElement) throws Exception {
/**
* The name of bean that annotated Dubbo's {@link Service @Service} in local Spring {@link ApplicationContext}
*/
/**
*
* 得到需要被注入的对象的BeanName,生成规则默认是,查看ServiceBeanNameBuilder.build()
* ServiceBean:${interfaceName}:${version}:${group}
*/
String referencedBeanName = buildReferencedBeanName(attributes, injectedType); /**
* The name of bean that is declared by {@link Reference @Reference} annotation injection
*/
/**
* 得到引用对象@ReferenceBean的BeanName ,
* 如果有Id 就是Id,没有通过generateReferenceBeanName()产生,产生的类名如下:
* @Reference(${attributes})${interface}
*/
String referenceBeanName = getReferenceBeanName(attributes, injectedType); /**
* 构建一个ReferenceBean 对象,如果不存在的话。
*
*/
ReferenceBean referenceBean = buildReferenceBeanIfAbsent(referenceBeanName, attributes, injectedType); /**
* 判断是否为本地ServiceBean,一般都是远程引用dubbo服务。
*/
boolean localServiceBean = isLocalServiceBean(referencedBeanName, referenceBean, attributes); /**
* 然后注册referenceBean
*/
registerReferenceBean(referencedBeanName, referenceBean, attributes, localServiceBean, injectedType); /**
* 把referenceBean 放入缓存中。
*/
cacheInjectedReferenceBean(referenceBean, injectedElement); /**
*
* 通过referenceBean 创建动态代理创建一个injectedType类型的对象。核心,创建一个代理对象,用来代表需要引用远程的Service服务
*/
return getOrCreateProxy(referencedBeanName, referenceBean, localServiceBean, injectedType);
}

上面内部方法调用都比较简单,逻辑也非常清楚,最终是通过getOrCreateProxy()方法来创建一个远程的代理对象,然后通过InjectedElement.inject注入该代理对象。

接着我们最好看下getOrCreateProxy()方法。

private Object getOrCreateProxy(String referencedBeanName, ReferenceBean referenceBean, boolean localServiceBean,
Class<?> serviceInterfaceType) {
/**
*
* 如果是本地就有服务Bean的话。
*/
if (localServiceBean) { // If the local @Service Bean exists, build a proxy of Service
//通过Proxy.newProxyInstance创建一个新的代理对象,在内部通过applicationContext获取本地Service即可。
return newProxyInstance(getClassLoader(), new Class[]{serviceInterfaceType},
newReferencedBeanInvocationHandler(referencedBeanName));
} else {
//如果是远程Service,判断被引用的服务referencedBeanName是否已经存在在applicationContext上,是的话,直接暴露服务,
// 基本上不太可能,因为在前面已经判断了被引用的服务Bean在远程,所以这里仅仅是为了防止localServiceBean判断错误。
exportServiceBeanIfNecessary(referencedBeanName); // If the referenced ServiceBean exits, export it immediately
//接着直接通过get()方法来创建一个代理,这get操作就会引入dubbo服务的订阅等相关内容。
return referenceBean.get();
}
}

可以看到最终调用了referenceBean.get()方法来创建一个远程本地代理对象。referenceBean.get()在下一个章节分析。

到这里,我们已经分析了@DubboReference注解的处理过程,然后知道了referenceBean.get()在Spring的postProcessPropertyValues扩展点上被调用。

Dubbo系列之 (四)服务订阅(1)的更多相关文章

  1. Dubbo源码(四) - 服务引用(消费者)

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 上一篇文章,讲了Dubbo的服务导出: Dubbo源码(三) - 服务导出(生产者) 本文,咱们 ...

  2. Dubbo系列讲解之服务注册【3万字长文分享】 23/100 发布文章

    服务注册的几个步骤   对于RPC框架的服务注册,一般包含了如下的流程: 加载服务提供者,可能是通过xml配置的,也可能是通过扫描注解的 实例化服务提供者,并以服务接口作为key,实现类作为value ...

  3. Dubbo源码(五) - 服务目录

    前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊聊Dubbo的服务目录(Directory).下面是官方文档对服务目录的定义: 服务目 ...

  4. Dubbo系列之 (五)服务订阅(2)

    辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...

  5. Dubbo系列之 (六)服务订阅(3)

    辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...

  6. dubbo系列四、dubbo服务暴露过程源码解析

    一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...

  7. Dubbo 系列(05-1)服务发布

    目录 Dubbo 系列(05-1)服务发布 Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 1.1 服务暴露整体机制 2. 源码分析 2.1 前置工作 2.2 ...

  8. Dubbo 系列(07-2)集群容错 - 服务路由

    目录 Dubbo 系列(07-2)集群容错 - 服务路由 1. 背景介绍 1.1 继承体系 1.2 SPI 2. 源码分析 2.1 创建路由规则 2.2 RouteChain 2.3 条件路由 Dub ...

  9. Dubbo 系列(07-1)集群容错 - 服务字典

    Dubbo 系列(07-1)集群容错 - 服务字典 [toc] Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 本篇文章,将开始分析 Dubbo 集群容错方面的 ...

随机推荐

  1. git pull & git fetch

    Git中从远程的分支获取最新的版本到本地有这样2个命令:1. git fetch:相当于是从远程获取最新版本到本地,不会自动merge   git fetch origin mastergit log ...

  2. Nginx(一)Linux上的Nginx安装步骤

    一.Windows下安装 解压:nginx-windows 双击: nginx.exe 能看到nginx欢迎界面说明,nginx安装成功 演示下 nginx做静态服务器 二.Linux下安装 (1). ...

  3. Java事务解析(事务的基本操作+隔离的等级+事务的四大特性+事务的概念)

    Java事务解析(事务的基本操作+隔离的等级+事务的四大特性+事务的概念) 什么是事务? 如果一个包含多个步骤的业务操作,这些操作被事务管理,那么这些操作要么同时成功要么同时失败 事务的四大特性(必须 ...

  4. 今天上午完成了devicescan,发送了rar包到yzx3233@sina.com

    今天上午完成了devicescan,发送了rar包到yzx3233@sina.com 可以正常扫描和输入了. 还有一个最后的问题,就是选择退出后,程序还在后台

  5. shell 十三问

    经典的Shell十三问 摘选整理自:http://bbs.chinaunix.net/thread-218853-1-1.htmlhttps://github.com/wzb56/13_questio ...

  6. Django学习路13_创建用户登录,判断数据库中账号名密码是否正确

    在 models.py 中设置数据库表的信息 from django.db import models # Create your models here. class User(models.Mod ...

  7. CSS两列布局的N种实现

    一.什么是两列布局 两列布局分为两种,一种是左侧定宽.右侧自适应,另一种是两列都自适应(即左侧宽度由子元素决定,右侧补齐剩余空间).在CSS面试题里面属于常考题,也是一个前端开发工程师必须掌握的技能, ...

  8. PHP curl_share_init函数

    (PHP 5 >= 5.5.0) curl_share_init — 初始化一个 cURL 共享句柄 说明 resource curl_share_init ( void ) 允许两个 cURL ...

  9. PHP mysqli_set_charset() 函数

    设置默认客户端字符集: <?php 高佣联盟 www.cgewang.com // 假定数据库用户名:root,密码:123456,数据库:RUNOOB $con=mysqli_connect( ...

  10. Skill 脚本演示 ycBusNet.il

    https://www.cnblogs.com/yeungchie/ ycBusNet.il 用于原理图中按照一定格式,批量创建 Bus . 回到目录