前言
Dubbo3 已经从一开始的 RPC 框架改头换面,现在的定位是微服务框架,除了提供基本的 RPC 功能外,它还提供了一整套的服务治理方案。
Dubbo 有自身的一套设计体系,不过通常很少单独使用,更多的是和 Spring 整合在一起,本文分析下 Dubbo3 整合 Spring Boot 的源码,版本是 3.1.10,Github 地址:github.com/apache/dubb…。
Dubbo3 专门提供了一个模块来和 Spring Boot 做整合,模块名是dubbo-spring-boot
,下面有4个子模块:
dubbo-spring-boot
-> dubbo-spring-boot-actuator
-> dubbo-spring-boot-autoconfigure
-> dubbo-spring-boot-compatible
-> dubbo-spring-boot-starter
一般我们直接引入dubbo-spring-boot-starter
即可开启 Dubbo 的自动装配,在 Spring Boot 项目里使用 Dubbo。
org.apache.dubbo
dubbo-spring-boot-starter
3.1.10
所以我们以这个模块为切入点,看看 Dubbo 做了什么。
入口模块
dubbo-spring-boot-starter
模块没有代码,只有一个pom.xml
引入了几个必要的依赖:
org.springframework.boot
spring-boot-starter
true
org.apache.logging.log4j
log4j-api
${log4j2_version}
true
org.apache.dubbo
dubbo-spring-boot-autoconfigure
${project.version}
我们直接看dubbo-spring-boot-autoconfigure
模块:
src/main
-> java/org/apache/dubbo/spring/boot/autoconfigure
---> BinderDubboConfigBinder.java
---> DubboRelaxedBinding2AutoConfiguration.java
-> resources/META-INF
---> spring.factories
这里用到了 Spring Boot 的自动装配功能,显然就两个类是无法实现如此复杂的整合的,再看pom.xml
发现该模块又依赖了dubbo-spring-boot-autoconfigure-compatible
模块:
org.apache.dubbo
dubbo-spring-boot-autoconfigure-compatible
${project.version}
该模块的spring.factories
配置了一堆组件:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration,
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration,
org.apache.dubbo.spring.boot.autoconfigure.DubboListenerAutoConfiguration
org.springframework.context.ApplicationListener=
org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener
org.springframework.boot.env.EnvironmentPostProcessor=
org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor
org.springframework.context.ApplicationContextInitializer=
org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer
OK,到这里看源码的思路基本就有了,Dubbo 利用 Spring Boot 自动装配的功能,提供了两个自动装配的模块,配置了一堆自动装配的组件,通过这些组件来和 Spring Boot 做整合。
服务发布
Dubbo3 整合 Spring 后,如何将加了@DubboService
的服务发布出去?
服务发布的关键节点:
DubboAutoConfiguration#serviceAnnotationBeanProcessor ServiceBean后置处理器
ServiceAnnotationPostProcessor#postProcessBeanDefinitionRegistry
ServiceAnnotationPostProcessor#scanServiceBeans 扫描ServiceBean
DubboClassPathBeanDefinitionScanner#scan
ServiceAnnotationPostProcessor#processScannedBeanDefinition 处理BeanDefinition
ServiceAnnotationPostProcessor#buildServiceBeanDefinition 构建ServiceBeanDefinition
BeanDefinitionRegistry#registerBeanDefinition 注册BeanDefinition
ServiceBean#afterPropertiesSet
ModuleConfigManager#addService 交给ModuleConfigManager管理 此时还没启动服务
DubboDeployApplicationListener#onApplicationEvent Spring事件监听
DubboDeployApplicationListener#onContextRefreshedEvent ContextRefreshed事件
DefaultModuleDeployer#start 模块部署启动
DefaultModuleDeployer#exportServices 启动服务 暴露服务
DefaultModuleDeployer#referServices 引用服务
AutoConfiguration
先看dubbo-spring-boot-autoconfigure
模块自动装配的配置:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
DubboRelaxedBinding2AutoConfiguration 是 Dubbo 提供的针对 Spring Boot 2.0 的自动装配类,它先于 DubboRelaxedBindingAutoConfiguration 执行,如果你用的是 Spring Boot 1.x 版本,则会触发后者。
核心:读取dubbo.scan.basePackages
配置,获取要扫描的包路径。
@ConditionalOnMissingBean(name = BASE_PACKAGES_BEAN_NAME)
@Bean(name = BASE_PACKAGES_BEAN_NAME)
public Set dubboBasePackages(ConfigurableEnvironment environment) {
// 读取 dubbo.scan.basePackages 扫描包路径 注册Set到Spring容器
PropertyResolver propertyResolver = dubboScanBasePackagesPropertyResolver(environment);
return propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
}
@ConditionalOnMissingBean(name = RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME, value = ConfigurationBeanBinder.class)
@Bean(RELAXED_DUBBO_CONFIG_BINDER_BEAN_NAME)
@Scope(scopeName = SCOPE_PROTOTYPE)
public ConfigurationBeanBinder relaxedDubboConfigBinder() {
return new BinderDubboConfigBinder();
}
再看 DubboAutoConfiguration,它主要做的事:
- 注入 ServiceAnnotationPostProcessor,处理 ServiceBean
- DubboSpringInitializer 的初始化,注册一些核心bean
@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true)
@Configuration
@AutoConfigureAfter(DubboRelaxedBindingAutoConfiguration.class)
@EnableConfigurationProperties(DubboConfigurationProperties.class)
@EnableDubboConfig// 引入 DubboConfigConfigurationRegistrar
public class DubboAutoConfiguration {
@ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
@ConditionalOnBean(name = BASE_PACKAGES_BEAN_NAME)
@Bean
public ServiceAnnotationPostProcessor serviceAnnotationBeanProcessor(@Qualifier(BASE_PACKAGES_BEAN_NAME)
Set packagesToScan) {
// 获取扫描的包路径
// 注入 ServiceBean 后置处理器
return new ServiceAnnotationPostProcessor(packagesToScan);
}
}
ServiceAnnotationPostProcessor
Dubbo 对外提供的服务,在 Spring 里面被封装为org.apache.dubbo.config.spring.ServiceBean
。
它继承自 ServiceConfig,所以它是一个标准的 Dubbo Service,在整合 Spring 方面做了扩展。
ServiceAnnotationPostProcessor 是 Spring BeanFactory 的后置处理器,它的职责是:扫描 DubboService Bean,注册到 Spring 容器。
拿到需要扫描的包路径,然后扫描 DubboService Bean:
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
this.registry = registry;
// 扫描 DubboService Bean
scanServiceBeans(resolvedPackagesToScan, registry);
}
Dubbo 会在类路径下扫描,所以会 new 一个 DubboClassPathBeanDefinitionScanner 扫描器,它依赖于 Spring 的 ClassPathBeanDefinitionScanner,将类路径下加了@DubboService
、@Service
的类封装成 BeanDefinition,然后注册到容器:
private void scanServiceBeans(Set packagesToScan, BeanDefinitionRegistry registry) {
// 实例化一个ClassPath扫描器
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 扫描bean的包含规则 有@DubboService @Service注解
for (Class anInterface : internalInterfaces) {
proxyFactory.addInterface(anInterface);
}
if (!StringUtils.isEquals(interfaceClass.getName(), interfaceName)) {
try {
Class serviceInterface = ClassUtils.forName(interfaceName, beanClassLoader);
proxyFactory.addInterface(serviceInterface);
} catch (ClassNotFoundException e) {
}
}
this.lazyProxy = proxyFactory.getProxy(this.beanClassLoader);
}
调用 LazyProxy 的方法会触发JdkDynamicAopProxy#invoke()
,它会在第一次调用非 Object 方法时创建实际的对象。
创建实际的对象由 DubboReferenceLazyInitTargetSource 完成:
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {
@Override
protected Object createObject() throws Exception {
// 首次调用 ReferenceBean 方法才会创建
return getCallProxy();
}
@Override
public synchronized Class getTargetClass() {
return getInterfaceClass();
}
}
getCallProxy()
其实就是调用ReferenceConfig#get()
引用服务。
private Object getCallProxy() throws Exception {
if (referenceConfig == null) {
throw new IllegalStateException("ReferenceBean is not ready yet, please make sure to call reference interface method after dubbo is started.");
}
synchronized (((DefaultSingletonBeanRegistry)getBeanFactory()).getSingletonMutex()) {
return referenceConfig.get();
}
}
尾巴
Dubbo3 和 Spring Boot 整合的过程可谓是一波三折,过程还是挺绕的,除了要了解 Dubbo 的底层原理,还要对 Spring Boot 的原理、各种后置处理器很熟悉才行。
Dubbo 首先是提供了一个单独的模块来和 Spring Boot 做整合,利用 Spring Boot 自动装配的功能,配置了一堆自动装配的组件。
首先是读取到要扫描的包路径,然后扫描 DubboService Bean,并把它注册到 Spring 容器,而后统一交给 ModuleConfigManager 管理;再通过监听 ContextRefreshedEvent 事件来完成服务的启动和发布。
针对 DubboReference 的注入,则依赖 ReferenceAnnotationBeanPostProcessor 后置处理器,它会遍历 Spring 容器内所有的 BeanDefinition,然后查找注入点,把注入点封装成 ReferenceBean 并注册到 Spring 容器,依赖注入时再通过容器获取对应的 bean 实例。Dubbo 采用的是延迟注入的方式,默认会注入一个空壳的 LazyProxy 对象,在首次调用 RPC 方法时再去调用底层的服务引用方法。