文章目录
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方法
@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的关注点更加集中。