文章目录
一、前言
前一篇《Spring Boot整合Dubbo Consumer》介绍了Dubbo Consumer与Spring Boot的整合流程,这一篇接着补齐Spring Boot整合Dubbo Provider。Consumer和Provider各自与Spring整合的流程比较独立,分别用到的是Spring两大核心扩展机制:前者是基于BeanPostProcessor,后者是基于BeanFactoryPostProcessor扩展机制。不仅仅只Dubbo,其他开源组件与Spring整合(如MyBatis、Kafka),基本也是这利用两种扩展机制。故本文虽说是讲Dubbo与Spring整合,但更多的是希望读者通过Dubbo这个具体case,熟悉、掌握Spring两大扩展机制,更加深刻地理解Spring。
二、Provider 整合
Consumer整合,利用的是 ReferenceAnnotationBeanPostProcessor 这种由Spring提供的BeanPostProcessor扩展机制;同样,Provider整合也是充分利用了Spring扩展机制,不过稍有不同的是,这次使用的是基于BeanDefinition
的扩展机制BeanFactoryPostProcessor
。这里有必要补充下 BeanPostProcessor 和 BeanFactoryPostProcessor 的区别:
- BeanPostProcessor: 主要作用于Bean对象生命周期中
实例化
(对象实例化前、后)、初始化
(Bean初始化前、后)这两个阶段;对Spring Bean的生命周期还不太清晰的话,可参加楼主之前的博文【俯瞰Spring】二、Bean的生命周期; - BeanFactoryPostProcessor: 作用于Bean对象的BeanDefinition的生成和注册阶段;
- BeanPostProcessor提供的方法在每一个Bean的生命周期中都会触发一次调用,也就是说如果有100个Bean,每个BeanPostProcessor的各生命周期回调方法都会被触发100次;而BeanFactoryPostProcessor可理解为容器级别的扩展机制,整个ioc容器启动过程中,仅随ioc容器启动触发一次。
- 在Spring中,是先有BeanDefinition,然后根据BeanDefinition创建出Bean对象的,故BeanFactoryPostProcessor 的触发时间先与BeanPostProcessor。
2.1 ServiceAnnotationBeanPostProcessor 类层次结构
具体而言,在Apache官方提供的dubbo-spring-boot-starter中,使用到的是BeanFactoryPostProcessor
的实现类ServiceAnnotationBeanPostProcessor
;这里吐槽下ServiceAnnotationBeanPostProcessor
这个名字取得有歧义,本身并非BeanPostProcessor子类,而是BeanFactoryPostProcessor的子类,按Spring的命名习惯,叫XXXFacotroyPostProcessor应该更贴切!
从ServiceAnnotationBeanPostProcessor 的类层次结构图,可以看到其实现了 BeanDefinitionRegistryPostProcessor
这个接口
-BeanDefinitionRegistryPostProcessor
作用:在bean实例之前,对ApplicationContext已存在的BeanDefinition进行修改,也可以用来继续向ApplicationContext生成并注册新的BeanDefinition
public interface BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor {
/**
* Modify the application context's internal bean definition registry after its
* standard initialization. All regular bean definitions will have been loaded,
* but no beans will have been instantiated yet. This allows for adding further
* bean definitions before the next post-processing phase kicks in.
* @param registry the bean definition registry used by the application context
* @throws org.springframework.beans.BeansException in case of errors
*/
void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException;
}
2.2 Provider 整合流程
简要概括下:
-
1、
@EnableDubbo
注解组合了@DubboComponentScan
、@EnableDubboConfig
两个注解; -
2、一方面,
@EnableDubboConfig
通过import机制引入了DubboConfigConfigurationRegistrar
,注册了DubboConfigConfiguration.Multiple 或 DubboConfigConfiguration.Single 的BeanDefition;这条分支可理解为Dubbo是在为其启动加载配置文件; -
3、另一方面,
@DubboComponentScan
通过import机制引入了DubboComponentScanRegistrar
,其做了两件事:a.向容器导入了Dubbo启动环境需要的5大组件(上图天蓝色部分); b.向容器导入了ServiceAnnotationBeanPostProcessor
这个BeanFactoryPostProcessor组件,当容器启动BeanDefiniton加载解析阶段,用来做包扫描,对扫描到的带Dubbo的@Service 注解的类,生成BeanDefinition, 并注册到容器。这一步就是Dubbo Service托管到Spring的核心原理! -
4、上面3个步骤,发生在Bean定义解析、注册阶段,除BeanPostProcessor类型组件外,其Dubbo Serice对应的Bean还未开始实例化。当Spring Ioc容器完成刷新时,会触发
ContextRefreshedEvent
事件,DubboBootstrapApplicationListener
接收到事件后,直接调用DubboBootstrap的start()
方法,全部Dubbo Service服务导出逻辑就统一收敛在该方法里面!
经过以上步骤,Dubbo Service对应的bean已托管到Spring,并且也DubboBootstrap.start()
之后,所以的Dubbo服务都已完成服务导出。
2.3 源码佐证
2.3.1 注解驱动
结合源码串一下2.2节中提到的几个核心注解
- @EnableDubbo 注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
@AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
boolean multipleConfig() default true;
}
- @DubboComponentScan 注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
- @EnableDubboConfig 注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {
/**
* It indicates whether binding to multiple Spring Beans.
*
* @return the default value is <code>false</code>
* @revised 2.5.9
*/
boolean multiple() default true;
}
2.3.2 Dubbo配置文件加载
通过 DubboConfigConfigurationRegistrar
,导入的 DubboConfigConfiguration.Single 或 DubboConfigConfiguration.Multiple,集中完成Dubbo所需的Application、Regsitry等配置文件加载
- DubboConfigConfiguration
public class DubboConfigConfiguration {
/**
* Single Dubbo {@link AbstractConfig Config} Bean Binding
*/
@EnableConfigurationBeanBindings({
@EnableConfigurationBeanBinding(prefix = "dubbo.application", type = ApplicationConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.module", type = ModuleConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.registry", type = RegistryConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.protocol", type = ProtocolConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.monitor", type = MonitorConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.provider", type = ProviderConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.consumer", type = ConsumerConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.config-center", type = ConfigCenterBean.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.metadata-report", type = MetadataReportConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.metrics", type = MetricsConfig.class),
@EnableConfigurationBeanBinding(prefix = "dubbo.ssl", type = SslConfig.class)
})
public static class Single {
}
/**
* Multiple Dubbo {@link AbstractConfig Config} Bean Binding
*/
@EnableConfigurationBeanBindings({
@EnableConfigurationBeanBinding(prefix = "dubbo.applications", type = ApplicationConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.modules", type = ModuleConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.registries", type = RegistryConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.protocols", type = ProtocolConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.monitors", type = MonitorConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.providers", type = ProviderConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.consumers", type = ConsumerConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.config-centers", type = ConfigCenterBean.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.metadata-reports", type = MetadataReportConfig.class, multiple = true),
@EnableConfigurationBeanBinding(prefix = "dubbo.metricses", type = MetricsConfig.class, multiple = true)
})
public static class Multiple {
}
}
2.3.3 ServiceAnnotationBeanPostProcessor 包扫描
包扫描要做的事情,就是筛选出带 Dubbo @Service 注解的Java类,逐一生成对应的 BeanDefinition,并进行注册。
- 导入 ServiceAnnotationBeanPostProcessor
public class DubboComponentScanRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
Set<String> packagesToScan = getPackagesToScan(importingClassMetadata);
// 导入 ServiceAnnotationBeanPostProcessor 组件
registerServiceAnnotationBeanPostProcessor(packagesToScan, registry);
// @since 2.7.6 Register the common beans
registerCommonBeans(registry);
}
/**
* Registers {@link ServiceAnnotationBeanPostProcessor}
*
* @param packagesToScan packages to scan without resolving placeholders
* @param registry {@link BeanDefinitionRegistry}
* @since 2.5.8
*/
private void registerServiceAnnotationBeanPostProcessor(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
// 1.生成 ServiceAnnotationBeanPostProcessor 对应的BeanDefinition
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceAnnotationBeanPostProcessor.class);
// 指定要扫描的包
builder.addConstructorArgValue(packagesToScan);
builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 2.注册 BeanDefinition
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinition, registry);
}
private Set<String> getPackagesToScan(AnnotationMetadata metadata) {
// 常用套路: 解析得到注解的属性信息
AnnotationAttributes attributes = AnnotationAttributes.fromMap(
metadata.getAnnotationAttributes(DubboComponentScan.class.getName()));
String[] basePackages = attributes.getStringArray("basePackages");
Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses");
String[] value = attributes.getStringArray("value");
// Appends value array attributes
Set<String> packagesToScan = new LinkedHashSet<String>(Arrays.asList(value));
packagesToScan.addAll(Arrays.asList(basePackages));
for (Class<?> basePackageClass : basePackageClasses) {
packagesToScan.add(ClassUtils.getPackageName(basePackageClass));
}
if (packagesToScan.isEmpty()) {
// 未指定包名,则以当前类所在的包名作为扫描起点
return Collections.singleton(ClassUtils.getPackageName(metadata.getClassName()));
}
return packagesToScan;
}
}
- 包扫描
真正进入到包扫描执行流程了,这块知识比较硬核,需耐心点看懂。
public class ServiceAnnotationBeanPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware,
ResourceLoaderAware, BeanClassLoaderAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// @since 2.7.5
registerBeans(registry, DubboBootstrapApplicationListener.class);
// 要扫描的包:packagesToScan是从构造函数传入的
Set<String> resolvedPackagesToScan = resolvePackagesToScan(packagesToScan);
if (!CollectionUtils.isEmpty(resolvedPackagesToScan)) {
// 筛选出 @Service 注解的类,生成BeanDefinition并注册
registerServiceBeans(resolvedPackagesToScan, registry);
} else {
if (logger.isWarnEnabled()) {
logger.warn("packagesToScan is empty , ServiceBean registry will be ignored!");
}
}
}
private void registerServiceBeans(Set<String> packagesToScan, BeanDefinitionRegistry registry) {
// 【核心】1.自定义一个包扫描器(其实大部分就是覆写父类方法,最大的改动就是禁止使用默认的筛选器, 即useDefaultFilters=false)
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
BeanNameGenerator beanNameGenerator = resolveBeanNameGenerator(registry);
scanner.setBeanNameGenerator(beanNameGenerator);
// 2.指定包扫描通过 @Service 来过滤筛选!
scanner.addIncludeFilter(new AnnotationTypeFilter(Service.class));
/**
* Add the compatibility for legacy Dubbo's @Service
*
* The issue : https://github.com/apache/dubbo/issues/4330
* @since 2.7.3
*/
scanner.addIncludeFilter(new AnnotationTypeFilter(com.alibaba.dubbo.config.annotation.Service.class));
for (String packageToScan : packagesToScan) {
// Registers @Service Bean first
scanner.scan(packageToScan);
// 3.粗略生成全部 @Service 注解类的 BeanDefinition;
// 这时的 BeanDefinition 实际类型都是 ScannedGenericBeanDefinition,没什么特别有价值的信息,还需要再加工
// 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) {
// 【核心】4、进一步加工并注册 BeanDefinition
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 + "]");
}
}
}
}
private void registerServiceBean(BeanDefinitionHolder beanDefinitionHolder, BeanDefinitionRegistry registry,
DubboClassPathBeanDefinitionScanner scanner) {
Class<?> beanClass = resolveClass(beanDefinitionHolder);
Annotation service = findServiceAnnotation(beanClass);
// 解析类上面的 @Service注解,得到属性信息
AnnotationAttributes serviceAnnotationAttributes = getAnnotationAttributes(service, false, false);
Class<?> interfaceClass = resolveServiceInterfaceClass(serviceAnnotationAttributes, beanClass);
String annotatedServiceBeanName = beanDefinitionHolder.getBeanName();
// 对包扫描得到的 ScannedGenericBeanDefinition 进一步加工,转换成beanClass为ServiceBean 的 BeanDefinition
AbstractBeanDefinition serviceBeanDefinition =
buildServiceBeanDefinition(service, serviceAnnotationAttributes, interfaceClass, annotatedServiceBeanName);
// ServiceBean Bean name
String beanName = generateServiceBeanName(serviceAnnotationAttributes, interfaceClass);
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?");
}
}
}
private AbstractBeanDefinition buildServiceBeanDefinition(Annotation serviceAnnotation,
AnnotationAttributes serviceAnnotationAttributes,
Class<?> interfaceClass,
String annotatedServiceBeanName) {
// 对 ScannedGenericBeanDefinition 进一步加工,转换成beanClass为ServiceBean 的 BeanDefinition
BeanDefinitionBuilder builder = rootBeanDefinition(ServiceBean.class);
AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
// 后面的逻辑都是往 propertyValues 塞一些数据,省略部分。。。
MutablePropertyValues propertyValues = beanDefinition.getPropertyValues();
String[] ignoreAttributeNames = of("provider", "monitor", "application", "module", "registry", "protocol",
"interface", "interfaceName", "parameters");
propertyValues.addPropertyValues(new AnnotationPropertyValuesAdapter(serviceAnnotation, environment, ignoreAttributeNames));
// References "ref" property to annotated-@Service Bean
addPropertyReference(builder, "ref", annotatedServiceBeanName);
// Set interface
builder.addPropertyValue("interface", interfaceClass.getName());
// Convert parameters into map
// 后面的逻辑都是往 propertyValues 塞一些数据,省略部分。。。
return builder.getBeanDefinition();
}
}
至此,就完成了Dubbo原生的 @Service 注解的包扫描,依次为每个类并生成、注册了beanClass为ServiceBean的BeanDefinition;当Spring Ioc容器完成刷新后,遍历这些BeanDefiniton,Spring就会完成对应Bean的实例化、依赖注入、初始化生命周期,各ServiceBean也统一交给Spring容器托管了。
三、Provider 侧服务导出
上面已经理清Dubbo Service托管到Spring的过程,这一小节再单独分析下Dubbo服务是在哪个环节执行了服务导出。
3.1 服务导出
为了将Dubbo服务导出流程与Dubbo Service托管到Spring的流程解耦,这里又会见识到Spring提供的第三种扩展机制:事件监听!对照本文最开始的那副图,可以看到DubboBootstrapApplicationListener 这个对象是Spring容器一启动就注册了。当IoC容器初始化完成时,会触发ContextRefreshedEvent事件,该事件监听器接收到此类事件后,直接调用DubboBootstrap.start()方法完成全部Dubbo Service的服务导出。
- DubboBootstrapApplicationListener 事件监听器
public class DubboBootstrapApplicationListener extends OneTimeExecutionApplicationContextEventListener
implements Ordered {
public static final String BEAN_NAME = "dubboBootstrapApplicationListener";
private final DubboBootstrap dubboBootstrap;
public DubboBootstrapApplicationListener() {
// DubboBootstrap 是一个单例,Dubbo启动环境统一
this.dubboBootstrap = DubboBootstrap.getInstance();
}
@Override
public void onApplicationContextEvent(ApplicationContextEvent event) {
if (event instanceof ContextRefreshedEvent) {
// 处理上下文刷新事件
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
dubboBootstrap.start();
}
private void onContextClosedEvent(ContextClosedEvent event) {
dubboBootstrap.stop();
}
@Override
public int getOrder() {
return LOWEST_PRECEDENCE;
}
}
- DubboBootstrap 启动类
public class DubboBootstrap extends GenericEventListener {
private static DubboBootstrap instance;
private AtomicBoolean initialized = new AtomicBoolean(false);
private AtomicBoolean started = new AtomicBoolean(false);
/**
* See {@link ApplicationModel} and {@link ExtensionLoader} for why DubboBootstrap is designed to be singleton.
*/
public static synchronized DubboBootstrap getInstance() {
if (instance == null) {
instance = new DubboBootstrap();
}
return instance;
}
/**
* Start the bootstrap
*/
public DubboBootstrap start() {
// CAS机制保证只启动一次
if (started.compareAndSet(false, true)) {
// DubboBootstrap 初始化全流程,妥妥滴模板方法;也用到了CAS机制保证只初始化一次
initialize();
if (logger.isInfoEnabled()) {
logger.info(NAME + " is starting...");
}
// 【核心】 导出Dubbo服务
// 1. export Dubbo Services
exportServices();
// Not only provider register
if (!isOnlyRegisterProvider() || hasExportedServices()) {
// 2. export MetadataService
exportMetadataService();
//3. Register the local ServiceInstance if required
registerServiceInstance();
}
referServices();
if (logger.isInfoEnabled()) {
logger.info(NAME + " has started.");
}
}
return this;
}
@Deprecated
public void init() {
initialize();
}
/**
* Initialize
*/
private void initialize() {
if (!initialized.compareAndSet(false, true)) {
return;
}
ApplicationModel.initFrameworkExts();
startConfigCenter();
useRegistryAsConfigCenterIfNecessary();
loadRemoteConfigs();
checkGlobalConfigs();
initMetadataService();
initEventListener();
if (logger.isInfoEnabled()) {
logger.info(NAME + " has been initialized!");
}
}
private void exportServices() {
configManager.getServices().forEach(sc -> {
// TODO, compatible with ServiceConfig.export()
ServiceConfig serviceConfig = (ServiceConfig) sc;
serviceConfig.setBootstrap(this);
if (exportAsync) {
// 异步服务导出
ExecutorService executor = executorRepository.getServiceExporterExecutor();
Future<?> future = executor.submit(() -> {
sc.export();
});
asyncExportingFutures.add(future);
} else {
// 同步服务导出
sc.export();
exportedServices.add(sc);
}
});
}
}
四、总结
前一篇《Spring Boot整合Dubbo Consumer》 加上本篇,分别以图示+源码的方式,分析了Spring Boot与Dubbo Consumer、Provider整合流程及实现机制,实现思路概括下来就是充分利用Spring的两大核心扩展机制:BeanPostProcessor
和 BeanFactoryPostProcessor
- 对于Dubbo Consumer,采用
ReferenceAnnotationBeanPostProcessor
,完成对 @Reference 属性或方法的依赖注入; - 对于Dubbo Provider,采用
ServiceAnnotationBeanPostProcessor
,完成对 @Service 注解的包扫描和BeanDefinition注册。
由于本文写作的重点放在探究Spring与Dubbo的整合上,故对于Dubbo consumer 的「服务引入」和provider的「服务导出」在前后两篇中均未做详细介绍。但如果要用好Dubbo,还是建议花时间钻研细节加深理解。通过这两篇文章,在了解Dubbo与Spring整合流程之外,楼主认为有以下(小)技巧是值得学习借鉴的:
-
如何自定义一个类注解,让Spring能将注解所在的类加载到IoC容器?(结合@Reference注解依赖注入过程、参照@Resource注解依赖注入实现原理)
-
如何自定义一个属性或注解,让Spring能够对注解的字段或方法进行依赖注入?(参照Dubbo @Service 包扫描器,再了解下Mybatis的@MapperScan实现原理)
-
如何保证Spring事件监听器不重复处理事件(参照org.apache.dubbo.config.spring.context.OneTimeExecutionApplicationContextEventListener)
-
获得一个类带某种注解的属性或方法(见org.springframework.util.ReflectionUtils)
码字不易,如果有收获,顺手点个赞呗。全文完~