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扩展点上被调用。