文章目录
- Spring中Dubbo实现RPC
- 如何加载@Reference和@DubboReference注解
- DubboComponentScanRegistrar
- 概括
- 发现@Refrence和@DubboReference注解,并编织RPC通信逻辑
- ReferenceAnnotationBeanPostProcessor(核心)
- @Reference
- @DubboReference
- @Reference和@DubboReference的区别
- @DubboReference的使用方式
- 总结
Spring中Dubbo实现RPC
在使用dubbo的过程中,我们的用法是在Service中引入 远程接口,然后标记上注解写入名称与版本号等一系列配置信息,然后在需要进行RPC通信的地方调用接口方法,然后就能获取到信息了。
那么按照这种思路,就应该有下列几个步骤需要完成
- 发现使用dubbo注解的成员与方法并托管于spring容器
- 在对象实例中找到有指定注解的成员变量或方法
- 通过动态代理的方式,编织通信代码到访问逻辑中
当然,上述是主要脉络,细分的话还有 序列化问题,地址的负载均衡问题,访问策略问题等等,本文主要讲述的就是上面的主要脉络。
这里主要是对@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方法
@Overridepublic 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 @DubboComponentScanSet<String> packagesToScan = getPackagesToScan0(metadata, DubboComponentScan.class, "basePackages", "basePackageClasses");// get from @EnableDubbo, compatible with spring 3.xif (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的处理流程:
@Overridepublic 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的处理流程 , 也是两步
第一步,从容器中获取反射对象,如果没有,则重复发现步骤
第二步,就是进行动态代理,并注入
@Overridepublic 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方法,注意:下面是旧版本中的代码
@Overrideprotected 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中获取
@Overrideprotected 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的关注点更加集中。