Dubbo3 已经从一开始的 RPC 框架改头换面,现在的定位是微服务框架,除了提供基本的 RPC 功能外,它还提供了一整套的服务治理方案。
Dubbo 有自身的一套设计体系,不过通常很少单独使用,更多的是和 Spring 整合在一起,本文分析下 Dubbo3 整合 Spring Boot 的源码,版本是 3.1.10,Github 地址:https://github.com/apache/dubbo/tree/dubbo-3.1.10。
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。
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-starterartifactId>
<version>3.1.10version>
dependency>
所以我们以这个模块为切入点,看看 Dubbo 做了什么。
dubbo-spring-boot-starter
模块没有代码,只有一个pom.xml
引入了几个必要的依赖:
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starterartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-apiartifactId>
<version>${log4j2_version}version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-autoconfigureartifactId>
<version>${project.version}version>
dependency>
dependencies>
我们直接看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
模块:
<dependency>
<groupId>org.apache.dubbogroupId>
<artifactId>dubbo-spring-boot-autoconfigure-compatibleartifactId>
<version>${project.version}version>
dependency>
该模块的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 引用服务
先看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<String> 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,它主要做的事:
@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<String> packagesToScan) {
// 获取扫描的包路径
// 注入 ServiceBean 后置处理器
return new ServiceAnnotationPostProcessor(packagesToScan);
}
}
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<String> packagesToScan, BeanDefinitionRegistry registry) {
// 实例化一个ClassPath扫描器
DubboClassPathBeanDefinitionScanner scanner =
new DubboClassPathBeanDefinitionScanner(registry, environment, resourceLoader);
// 扫描bean的包含规则 有@DubboService @Service注解
for (Class<? extends Annotation> annotationType : serviceAnnotationTypes) {
scanner.addIncludeFilter(new AnnotationTypeFilter(annotationType));
}
// 排除规则
ScanExcludeFilter scanExcludeFilter = new ScanExcludeFilter();
scanner.addExcludeFilter(scanExcludeFilter);
for (String packageToScan : packagesToScan) {
// 扫描包路径
scanner.scan(packageToScan);
Set<BeanDefinitionHolder> beanDefinitionHolders =
findServiceBeanDefinitionHolders(scanner, packageToScan, registry, beanNameGenerator);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
// 处理扫描到的BeanDefinition -> 注册
processScannedBeanDefinition(beanDefinitionHolder);
servicePackagesHolder.addScannedClass(beanDefinitionHolder.getBeanDefinition().getBeanClassName());
}
servicePackagesHolder.addScannedPackage(packageToScan);
}
}
ServiceBean 注册到容器了,Spring 会正常实例化 bean。又因为 ServiceBean 实现了 InitializingBean 接口,所以会触发它的afterPropertiesSet()
,ServiceBean 会把自己交给 ModuleConfigManager 管理。
Tips:服务现在还没启动,目前只是先收集 ServiceBean。
public void afterPropertiesSet() throws Exception {
if (StringUtils.isEmpty(getPath())) {
if (StringUtils.isNotEmpty(getInterface())) {
setPath(getInterface());
}
}
//register service bean
ModuleModel moduleModel = DubboBeanUtils.getModuleModel(applicationContext);
// 交给 ModuleConfigManager 管理
moduleModel.getConfigManager().addService(this);
moduleModel.getDeployer().setPending();
}
DubboConfigConfigurationRegistrar 会初始化 DubboSpringInitializer,初始化的时候会向 Spring 容器注册一堆 bean,其中就包含 DubboDeployApplicationListener。
它是 Spring 应用程序的事件监听器,它监听到 ContextRefreshedEvent 事件会启动 Dubbo 服务;监听到 ContextClosedEvent 事件关闭 Dubbo 服务。
public void onApplicationEvent(ApplicationContextEvent event) {
if (nullSafeEquals(applicationContext, event.getSource())) {
if (event instanceof ContextRefreshedEvent) {
onContextRefreshedEvent((ContextRefreshedEvent) event);
} else if (event instanceof ContextClosedEvent) {
onContextClosedEvent((ContextClosedEvent) event);
}
}
}
启动服务其实就是触发ModuleDeployer#start()
,Dubbo 会启动相关组件,然后暴露服务和应用服务。
private void onContextRefreshedEvent(ContextRefreshedEvent event) {
ModuleDeployer deployer = moduleModel.getDeployer();
Assert.notNull(deployer, "Module deployer is null");
// 启动模块部署
Future future = deployer.start();
if (!deployer.isBackground()) {
try {
// 等待启动完成
future.get();
} catch (InterruptedException e) {
logger.warn(CONFIG_FAILED_START_MODEL, "", "", "Interrupted while waiting for dubbo module start: " + e.getMessage());
} catch (Exception e) {
logger.warn(CONFIG_FAILED_START_MODEL, "", "", "An error occurred while waiting for dubbo module start: " + e.getMessage(), e);
}
}
}
至此,服务启动完毕。
Dubbo3 整合 Spring 后,如何注入加了@DubboReference
的属性/方法,完成服务引用?
服务引用的关键节点:
DubboConfigConfigurationRegistrar#registerBeanDefinitions
DubboSpringInitializer#initialize Dubbo引用初始化
DubboBeanUtils#registerCommonBeans 注册公共bean
ReferenceAnnotationBeanPostProcessor#postProcessBeanFactory 遍历BeanDefinition
AbstractAnnotationBeanPostProcessor#findInjectionMetadata 查找注入点
ReferenceAnnotationBeanPostProcessor#prepareInjection 准备注入
ReferenceAnnotationBeanPostProcessor#registerReferenceBean 注册ReferenceBean
ReferenceAnnotationBeanPostProcessor#postProcessPropertyValues bean属性值后置处理
AnnotatedInjectElement#inject 注入
ReferenceBean#getObject 获取注入对象
ReferenceBean#createLazyProxy 创建延迟代理
DubboReferenceLazyInitTargetSource#createObject 调用bean时才创建真正的Proxy
ReferenceConfig#get 引用服务 创建Proxy
DubboSpringInitializer 初始化的时候会注册 ReferenceAnnotationBeanPostProcessor 类,它的职责是完成@DubboReference
依赖的注入。
它是 BeanFactoryPostProcessor 的子类,是 Spring BeanFactory 的后置处理器,Spring 启动时会触发postProcessBeanFactory()
。
首先是遍历容器中所有的 BeanDefinition,解析 BeanClass 上的属性或方法是否有加相关注解,也就是寻找注入点。
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
// 遍历所有已注册的BeanDefinition > 查找注入点 > 准备注入
String[] beanNames = beanFactory.getBeanDefinitionNames();
for (String beanName : beanNames) {
Class<?> beanType;
if (beanFactory.isFactoryBean(beanName)) {
BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
if (beanType != null) {
// 查找注入点
AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, beanType, null);
// 准备注入
prepareInjection(metadata);
}
}
}
AnnotatedInjectionMetadata 元数据解析完以后,Spring 会把注入点封装成 ReferenceBean,同时注册到 Spring 容器,AnnotatedFieldElement 只是记录一下引用的 beanName,后续依赖注入时就可以直接从 Spring 容器获取对应的 bean 实例了。
protected void prepareInjection(AnnotatedInjectionMetadata metadata) throws BeansException {
try {
// 遍历注入点 注册 ReferenceBean 此时记录的只是一个referenceBeanName 还没注入
for (AnnotatedFieldElement fieldElement : metadata.getFieldElements()) {
if (fieldElement.injectedObject != null) {
continue;
}
Class<?> injectedType = fieldElement.field.getType();
AnnotationAttributes attributes = fieldElement.attributes;
String referenceBeanName = registerReferenceBean(fieldElement.getPropertyName(), injectedType, attributes, fieldElement.field);
fieldElement.injectedObject = referenceBeanName;
// 缓存起来
injectedFieldReferenceBeanCache.put(fieldElement, referenceBeanName);
}
}
}
postProcessPropertyValues()
才是真正的依赖注入:
public PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
try {
// 已经解析过了,这里直接从缓存获取
AnnotatedInjectionMetadata metadata = findInjectionMetadata(beanName, bean.getClass(), pvs);
prepareInjection(metadata);
// 依赖注入
metadata.inject(bean, beanName, pvs);
}
return pvs;
}
AnnotatedInjectionMetadata 会把收集到的注入点 挨个进行注入,最终调用AnnotatedInjectElement#inject()
。
protected void inject(Object bean, String beanName, PropertyValues pvs) throws Throwable {
// ReferenceBean#getObject() 拿到一个延迟代理对象
Object injectedObject = getInjectedObject(attributes, bean, beanName, getInjectedType(), this);
if (member instanceof Field) {// 属性注入
Field field = (Field) member;
ReflectionUtils.makeAccessible(field);
field.set(bean, injectedObject);
} else if (member instanceof Method) {// 方法注入
Method method = (Method) member;
ReflectionUtils.makeAccessible(method);
method.invoke(bean, injectedObject);
}
}
依赖注入的对象被封装为 ReferenceBean,它是一个 FactoryBean,所以在注入的时候,其实会调用ReferenceBean#getObject()
获取 bean 实例。
public T getObject() {
if (lazyProxy == null) {
createLazyProxy();
}
return (T) lazyProxy;
}
Dubbo 这里并没有直接去引用远程服务,而是先创建了一个 LazyProxy,Dubbo 希望在真正发起 RPC 调用时才去引用服务,为什么这么做呢?
LazyProxy 的创建过程,利用 JDK 动态代理,创建了一个空壳的代理对象:
private void createLazyProxy() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTargetSource(new DubboReferenceLazyInitTargetSource());
proxyFactory.addInterface(interfaceClass);
Class<?>[] internalInterfaces = AbstractProxyFactory.getInternalInterfaces();
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 方法时再去调用底层的服务引用方法。