Dubbo3和Spring Boot整合过程

2023年 10月 11日 90.2k 0

前言

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
image.png
它继承自 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 方法时再去调用底层的服务引用方法。

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论