Dubbo系列之 (四)服务订阅(1)
辅助链接
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)的更多相关文章
- Dubbo源码(四) - 服务引用(消费者)
前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 上一篇文章,讲了Dubbo的服务导出: Dubbo源码(三) - 服务导出(生产者) 本文,咱们 ...
- Dubbo系列讲解之服务注册【3万字长文分享】 23/100 发布文章
服务注册的几个步骤 对于RPC框架的服务注册,一般包含了如下的流程: 加载服务提供者,可能是通过xml配置的,也可能是通过扫描注解的 实例化服务提供者,并以服务接口作为key,实现类作为value ...
- Dubbo源码(五) - 服务目录
前言 本文基于Dubbo2.6.x版本,中文注释版源码已上传github:xiaoguyu/dubbo 今天,来聊聊Dubbo的服务目录(Directory).下面是官方文档对服务目录的定义: 服务目 ...
- Dubbo系列之 (五)服务订阅(2)
辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...
- Dubbo系列之 (六)服务订阅(3)
辅助链接 Dubbo系列之 (一)SPI扩展 Dubbo系列之 (二)Registry注册中心-注册(1) Dubbo系列之 (三)Registry注册中心-注册(2) Dubbo系列之 (四)服务订 ...
- dubbo系列四、dubbo服务暴露过程源码解析
一.代码准备 1.示例代码 参考dubbo系列二.dubbo+zookeeper+dubboadmin分布式服务框架搭建(windows平台) 2.简单了解下spring自定义标签 https://w ...
- Dubbo 系列(05-1)服务发布
目录 Dubbo 系列(05-1)服务发布 Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 1.1 服务暴露整体机制 2. 源码分析 2.1 前置工作 2.2 ...
- Dubbo 系列(07-2)集群容错 - 服务路由
目录 Dubbo 系列(07-2)集群容错 - 服务路由 1. 背景介绍 1.1 继承体系 1.2 SPI 2. 源码分析 2.1 创建路由规则 2.2 RouteChain 2.3 条件路由 Dub ...
- Dubbo 系列(07-1)集群容错 - 服务字典
Dubbo 系列(07-1)集群容错 - 服务字典 [toc] Spring Cloud Alibaba 系列目录 - Dubbo 篇 1. 背景介绍 本篇文章,将开始分析 Dubbo 集群容错方面的 ...
随机推荐
- C# WebClient几种常用方法的用法
1.UploadData方法(Content-Type:application/x-www-form-urlencoded) //创建WebClient 对象 WebClient ...
- ES搜索引擎-一篇文章就够了
toc: true title: 一周一个中间件-ES搜索引擎 date: 2019-09-19 18:43:36 tags: - 中间件 - 搜索引擎 前言 在众多搜索引擎中,solr,es是我所知 ...
- lua中单引号和双引号和/的输出的问题
lua单引号和双引号的问题 lua 中的 单引号 与 双引号 (" " 与 '') Lua除支持双引号("")表示字符串外, 也支持用单引号('') 注意: 如 ...
- isinstance方法判断可迭代和迭代器
from collections import Iterable print(isinstance([],Iterable)) print(isinstance( {}, Iterable)) pri ...
- 小波变换检测信号突变点的MATLAB实现
之前在不经意间也有接触过求突变点的问题.在我看来,与其说是求突变点,不如说是我们常常玩的"找不同".给你两幅图像,让你找出两个图像中不同的地方,我认为这其实也是找突变点在生活中的应 ...
- .NetCore 入门
.net core是什么? .net core是一个可以用来构建现代.可伸缩和高性能的跨平台软件应用程序的通用开发框架. 我们为什么要使用.net core,也就是说.net core有什么好处? 跨 ...
- SpringBoot 发送邮件和附件
作者:yizhiwaz 链接:www.jianshu.com/p/5eb000544dd7 源码:https://github.com/yizhiwazi/springboot-socks 其他文章: ...
- tracebace用法
介绍一下traceback 平时看到的程序的错误信息也就是traceback信息 举个简单例子: import traceback try: s = [1, 2, 3] print s[5] exce ...
- opencv图片缩放与镜像
一个练习的代码,先对图片进行缩放,然后再做镜像: import cv2 import numpy as np img = cv2.imread("src.jpg", 1) cv2. ...
- 家庭记账本APP开发准备(一)
1.登录界面 通过学习比较 登录界面采用线性布局(LinearLayout) 下面是相关源码 activity_main.xml ?xml version="1.0" encodi ...