使用 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 容器中,比如:
@Service
public 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()方法,这个方法会启动一个网络监听,从而实现服务发布。
评论