使用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 packagesToScan = getPackagesToScan(importingClassMetadata);
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
registerReferenceAnnotationBeanPostProcessor(registry);
}
......
}
ImportBeanDefinitionRegistrar是Spring提供的一种动态注入Bean的机制,和ImportSelector接口的功能类似,在refisterBeanDefinitions方法中,主要会实例化一些BeanDefinition并且注入到Spring IoC容器中。
我们继续看registerServiceAnnotationBeanPostProcessor方法,逻辑比较简单,就是把SerficeAnnotationBeanPostProcessor注册到容器:
private void registerServiceAnnotationBeanPostProcessor(Set 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 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 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 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()方法,这个方法会启动一个网络监听,从而实现服务发布。