使用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()方法,這個方法會啟動一個網絡監聽,從而實現服務發布。