写点什么

Dubbo 和 Spring 集成的原理

用户头像
Java收录阁
关注
发布于: 2020 年 05 月 10 日

使用 Dubbo 最方便的地方在于它可以和 Spring 非常方便的集成,实际上,Dubbo 对于配置的优化,也是随着 Spring 一同发展的,从最早的 XML 形式到后来的注解方式以及自动装配,都是在不断地简化开发过程来提高开发效率。


在 Spring Boot 集成 Dubbo 时,服务发布主要有以下几个步骤:


- 添加 dubbo-spring-boot-starter 依赖

- 定义 @org.apache.dubbo.config.annotation.Service 注解

- 声明 @DubboComponentScan,用于扫描 @Service 注解


其实不难猜出,Dubbo 中的 @Service 注解和 Spring 中提供的 @Service 注解功能类似,用于实现 Dubbo 服务的暴露,与它相对应的时 @Reference,它的作用类似于 Spring 中的 @Autowired 注解。


而 @DubboComponentScan 和 Spring 中的 @ComponentScan 作用类似,用于扫描 @Service、@Reference 等注解。


@DubboComponentScan 注解解析

DubboComponentScan 注解的定义如下:


@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Import(DubboComponentScanRegistrar.class)public @interface DubboComponentScan {	String[] value() default {};	String[] basePackages() default {};	Class<?>[] basePackageClasses() default {};}
复制代码

这个注解主要通过 @Import 导入一个 DubboComponentScanRegistrar 类。DubboComponentScanRegistrar 实现了 ImportBeanDefinitionRegistrar 接口,并且重写了 registerBeanDefinitions 方法。在 registerBeanDefinitions 方法中主要做了以下几件事:


- 获取扫描包的路径,默认扫描当前配置类所在的包

- 注册 @Service 注解的解析类

- 注册 @Reference 注解的解析类


public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {	@Override	public void refisterBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {		Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);		registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);		registerReferenceAnnotationBeanPostProcessor(registry);	}	......}
复制代码

ImportBeanDefinitionRegistrar 是 Spring 提供的一种动态注入 Bean 的机制,和 ImportSelector 接口的功能类似,在 refisterBeanDefinitions 方法中,主要会实例化一些 BeanDefinition 并且注入到 Spring IoC 容器中。


我们继续看 registerServiceAnnotationBeanPostProcessor 方法,逻辑比较简单,就是把 SerficeAnnotationBeanPostProcessor 注册到容器:

private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {		// 构建BeanDefinitionBuilder        BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);        builder.addConstructorArgValue(packagesToScan);        builder.setRole(2);        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();        // 把BeanDefinition注册到IoC容器中        BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);    }
复制代码

所以,@DubboComponentScan 只是诸如一个 ServiceAnnotationBeanPostProcessor 和一个 ReferenceAnnotationBeanPostProcessor 对象,那 Dubbo 服务的注解 @Service 是如何解析的呢?


其实,主要逻辑就在两个类中,ServiceAnnotationBeanPostProcessor 用于解析 @Service 注解,ReferenceAnnotationBeanPostProcessor 用于解析 @Reference 注解。


ServiceAnnotationBeanPostProcessor

ServiceAnnotationBeanPostProcessor 类的定义如下,它的核心逻辑就是解析 @Service 注解


public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware {	......}
复制代码

ServiceAnnotationBeanPostProcessor 实现了 4 个接口,EnvironmentAware, ResourceLoaderAware, BeanClassLoaderAware 这三个接口比较好理解,我们重点看一下 BeanDefinitionRegistryPostProcessor。


BeanDefinitionRegistryPostProcessor 接口继承自 BeanFactoryPostProcessor,是一种比较特殊的 BeanFactoryPostProcessor。BeanDefinitionRegistryPostProcessor 中的 postProcessBeanDefinitionRegistry 方法可以让我们实现自定义的注册 Bean 定义的逻辑。该方法主要做了以下几件事:


- 调用 registerBeans 注册 DubboBootstrapApplicationListener 类

- 通过 resolvePackagesToScan 对 packagesToScan 参数进行去空格处理,并把配置文件中配置的扫描参数也一起处理。

- 调用 registerServiceBeans 完成 Bean 的注册。


public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {        AnnotatedBeanDefinitionRegistryUtils.registerBeans(registry, new Class[]{DubboBootstrapApplicationListener.class});        Set<String> resolvedPackagesToScan = this.resolvePackagesToScan(this.packagesToScan);        if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {            this.registerServiceBeans(resolvedPackagesToScan, registry);        } else if (this.logger.isWarnEnabled()) {            this.logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");        }
}
复制代码

这个方法的核心逻辑都在 registerServiceBeans 这个方法中,这个方法会查找需要扫描的指定包里面有 @Service 注解的类并将其注册成 Bean。


- 定义 DubboClassPathBeanDefinitionScanner 扫描对象,扫描指定路径下的类,将符合条件的类装配到 IoC 容器中。

- BeanNameGenerator 是 Beans 体系中比较重要的一个组件,会通过一定的算法计算出需要装配的 Bean 的 name。

- addIncludeFilter 设置 Scan 的过滤条件,只扫描 @Service 注解修饰的类。

- 遍历指定的包,通过 findServiceBeanDefinitionHolders 查找 @Service 注解修饰的类。

- 通过 registerServiceBean 完成 Bean 的注册。


/**     * Registers Beans whose classes was annotated {@link Service}     *     * @param packagesToScan The base packages to scan     * @param registry       {@link BeanDefinitionRegistry}     */    private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
DubboClassPathBeanDefinitionScanner scanner = new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
for (String packageToScan : packagesToScan) {
// Registers @Service Bean first scanner.scan(packageToScan);
// Finds all BeanDefinitionHolders of @Service whether @ComponentScan scans or not. Set<BeanDefinitionHolder> beanDefinitionHolders = findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
if (!CollectionUtils.isEmpty(beanDefinitionHolders)) {
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) { registerServiceBean(beanDefinitionHolder, registry, scanner); }
if (logger.isInfoEnabled()) { logger.info(beanDefinitionHolders.size() + " annotated Dubbo's @Service Components { " + beanDefinitionHolders + " } were scanned under package[" + packageToScan + "]"); }
} else {
if (logger.isWarnEnabled()) { logger.warn("No Spring Bean annotating Dubbo's @Service was found under package[" + packageToScan + "]"); }
}
}
}
复制代码

上面的代码主要作用就是通过扫描指定路径下添加了 @Service 注解的类,通过 registerServiceBean 来注册 ServiceBean,整体来看,Dubbo 的注解扫描进行服务发布的过程,实际上就是基于 Spring 的扩展。


继续分析 registerServiceBean 方法:


private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,                                     DubboClassPathBeanDefinitionScanner scanner) {
Class<?> beanClass = resolveClass(beanDefinitionHolder);
Service service = findAnnotation(beanClass, Service.class);
Class<?> interfaceClass = resolveServiceInterfaceClass(beanClass, service);
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
AbstractBeanDefinition serviceBeanDefinition = buildServiceBeanDefinition(service, interfaceClass, annotatedServiceBeanName);
// ServiceBean Bean name String beanName = generateServiceBeanName(service, interfaceClass, annotatedServiceBeanName);
if (scanner.checkCandidate(beanName, serviceBeanDefinition)) { // check duplicated candidate bean registry.registerBeanDefinition(beanName, serviceBeanDefinition);
if (logger.isInfoEnabled()) { logger.info("The BeanDefinition[" + serviceBeanDefinition + "] of ServiceBean has been registered with name : " + beanName); }
} else {
if (logger.isWarnEnabled()) { logger.warn("The Duplicated BeanDefinition[" + serviceBeanDefinition + "] of ServiceBean[ bean name : " + beanName + "] was be found , Did @DubboComponentScan scan to same package in many times?"); }
}
}
复制代码


- resolveClass 获取 BeanDefinitionHolder 中的 Bean

- findServiceAnnotation 方法从 beanClass 类中找到 @Service 注解

- getAnnotationAttributes 方法获得注解中的属性,比如 loadBalance、cluster 等。

- resolveServiceInterfaceClass 方法用于获得 beanClass 对应的接口定义,其实在 @Service(interfaceClass=xxxx.class)注解的声明中也可以声明 interfaceClass,注解中声明的优先级最高,如果没有声明该属性,则会从父类中查找。

- annotatedServiceBeanName 代表 Bean 的名称。

- buildServiceBeanDefinition 用来构造 org.apache.dubbo.config.spring.ServiceBean 对象,每个 Dubbo 服务的发布最终都会出现一个 ServiceBean。

- 调用 registerBeanDefinition 将 ServiceBean 注入 Spring IoC 容器中。


从整个方法的分析来看,registerServiceBean 方法主要是把一个 ServiceBean 注入到 Spring IoC 容器中,比如:


@Servicepublic class HelloServiceImpl implements IHelloService {	......}
复制代码

它并不是像普通的 Bean 注入一样直接将 HelloServiceImpl 对象的实例注入容器,而是注入一个 ServiceBean 对象。对于 HelloServiceImpl 来说,它并不需要把自己注入 Spring IoC 容器中,而是需要把自己发布到网络上,提供给网络上的服务消费者来访问。那它是怎么发布到网络上的呢?


上面在 postProcessBeanDefinitionRegistry 方法中注册了 DubboBootstrapApplicationListener 事件监听 Bean。


public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener implements Ordered {    private final DubboBootstrap dubboBootstrap = DubboBootstrap.getInstance();
public DubboBootstrapApplicationListener() { }
public void onApplicationContextEvent(ApplicationContextEvent event) { if (event instanceof ContextRefreshedEvent) { this.onContextRefreshedEvent((ContextRefreshedEvent)event); } else if (event instanceof ContextClosedEvent) { this.onContextClosedEvent((ContextClosedEvent)event); }
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) { this.dubboBootstrap.start(); }
private void onContextClosedEvent(ContextClosedEvent event) { this.dubboBootstrap.stop(); }
public int getOrder() { return 2147483647; }}
复制代码

当所有的 Bean 都处理完成之后,Spring IoC 会发布一个事件,事件类型为 ComtextRefreshedEvent,当触发整个事件时,会调用 onContextRefreshedEvent 方法。在这个方法中,可以看到 Dubbo 服务启动的触发机制 dubboBootstrap.start()。从这个方法中会进入 org.apache.dubbo.config.ServiceConfig 类中的 export()方法,这个方法会启动一个网络监听,从而实现服务发布。


发布于: 2020 年 05 月 10 日阅读数: 251
用户头像

Java收录阁

关注

士不可以不弘毅,任重而道远 2020.04.30 加入

喜欢收集整理Java相关技术文档的程序员,欢迎关注同名微信公众号 Java收录 阁获取更多文章

评论

发布
暂无评论
Dubbo和Spring集成的原理