Dubbo3和Spring Boot整合过程

前言

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个子模块:

basic 复制代码
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。

xml 复制代码
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.1.10</version>
</dependency>

所以我们以这个模块为切入点,看看 Dubbo 做了什么。

入口模块

dubbo-spring-boot-starter模块没有代码,只有一个pom.xml引入了几个必要的依赖:

xml 复制代码
<dependencies>
    <!-- Spring Boot 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <optional>true</optional>
    </dependency>
    <!-- 日志 依赖 -->
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-api</artifactId>
        <version>${log4j2_version}</version>
        <optional>true</optional>
    </dependency>
    <!-- Dubbo 自动装配 -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-autoconfigure</artifactId>
        <version>${project.version}</version>
    </dependency>
</dependencies>

我们直接看dubbo-spring-boot-autoconfigure模块:

basic 复制代码
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模块:

xml 复制代码
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-autoconfigure-compatible</artifactId>
    <version>${project.version}</version>
</dependency>

该模块的spring.factories配置了一堆组件:

basic 复制代码
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的服务发布出去?

服务发布的关键节点:

java 复制代码
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模块自动装配的配置:

basic 复制代码
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配置,获取要扫描的包路径。

java 复制代码
@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,它主要做的事:

  • 注入 ServiceAnnotationPostProcessor,处理 ServiceBean
  • DubboSpringInitializer 的初始化,注册一些核心bean
java 复制代码
@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);
    }
}

ServiceAnnotationPostProcessor

Dubbo 对外提供的服务,在 Spring 里面被封装为org.apache.dubbo.config.spring.ServiceBean

它继承自 ServiceConfig,所以它是一个标准的 Dubbo Service,在整合 Spring 方面做了扩展。

ServiceAnnotationPostProcessor 是 Spring BeanFactory 的后置处理器,它的职责是:扫描 DubboService Bean,注册到 Spring 容器。

拿到需要扫描的包路径,然后扫描 DubboService Bean:

java 复制代码
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
    this.registry = registry;
    // 扫描 DubboService Bean
    scanServiceBeans(resolvedPackagesToScan, registry);
}

Dubbo 会在类路径下扫描,所以会 new 一个 DubboClassPathBeanDefinitionScanner 扫描器,它依赖于 Spring 的 ClassPathBeanDefinitionScanner,将类路径下加了@DubboService@Service的类封装成 BeanDefinition,然后注册到容器:

java 复制代码
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。

java 复制代码
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();
}

DubboDeployApplicationListener

DubboConfigConfigurationRegistrar 会初始化 DubboSpringInitializer,初始化的时候会向 Spring 容器注册一堆 bean,其中就包含 DubboDeployApplicationListener。

它是 Spring 应用程序的事件监听器,它监听到 ContextRefreshedEvent 事件会启动 Dubbo 服务;监听到 ContextClosedEvent 事件关闭 Dubbo 服务。

java 复制代码
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 会启动相关组件,然后暴露服务和应用服务。

java 复制代码
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的属性/方法,完成服务引用?

服务引用的关键节点:

java 复制代码
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

ReferenceAnnotationBeanPostProcessor

DubboSpringInitializer 初始化的时候会注册 ReferenceAnnotationBeanPostProcessor 类,它的职责是完成@DubboReference依赖的注入。

它是 BeanFactoryPostProcessor 的子类,是 Spring BeanFactory 的后置处理器,Spring 启动时会触发postProcessBeanFactory()

首先是遍历容器中所有的 BeanDefinition,解析 BeanClass 上的属性或方法是否有加相关注解,也就是寻找注入点。

java 复制代码
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 实例了。

java 复制代码
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()才是真正的依赖注入:

java 复制代码
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()

java 复制代码
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

依赖注入的对象被封装为 ReferenceBean,它是一个 FactoryBean,所以在注入的时候,其实会调用ReferenceBean#getObject()获取 bean 实例。

java 复制代码
public T getObject() {
    if (lazyProxy == null) {
        createLazyProxy();
    }
    return (T) lazyProxy;
}

Dubbo 这里并没有直接去引用远程服务,而是先创建了一个 LazyProxy,Dubbo 希望在真正发起 RPC 调用时才去引用服务,为什么这么做呢?

LazyProxy 的创建过程,利用 JDK 动态代理,创建了一个空壳的代理对象:

java 复制代码
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 完成:

java 复制代码
private class DubboReferenceLazyInitTargetSource extends AbstractLazyCreationTargetSource {

    @Override
    protected Object createObject() throws Exception {
        // 首次调用 ReferenceBean 方法才会创建
        return getCallProxy();
    }

    @Override
    public synchronized Class<?> getTargetClass() {
        return getInterfaceClass();
    }
}

getCallProxy()其实就是调用ReferenceConfig#get()引用服务。

java 复制代码
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 方法时再去调用底层的服务引用方法。

相关推荐
小林学习编程3 小时前
SpringBoot校园失物招领信息平台
java·spring boot·后端
愿你天黑有灯下雨有伞3 小时前
Spring Boot整合Kafka实战指南:从环境搭建到消息处理全解析
spring boot·kafka·linq
Clf丶忆笙4 小时前
SpringBoot异步处理@Async深度解析:从基础到高阶实战
spring boot
柯南二号6 小时前
【后端】SpringBoot用CORS解决无法跨域访问的问题
java·spring boot·后端
帮帮志7 小时前
vue实现与后台springboot传递数据【传值/取值 Axios 】
前端·vue.js·spring boot
杨不易呀8 小时前
Java面试高阶篇:Spring Boot+Quarkus+Redis高并发架构设计与性能优化实战
spring boot·redis·高并发·分布式锁·java面试·quarkus
Moshow郑锴9 小时前
Spring Boot 3 + Undertow 服务器优化配置
服务器·spring boot·后端
码农飞哥9 小时前
互联网大厂Java面试实战:Spring Boot到微服务的技术问答解析
java·数据库·spring boot·缓存·微服务·消息队列·面试技巧
愿你天黑有灯下雨有伞11 小时前
Spring Boot集成RabbitMQ高级篇:可靠性与性能提升
spring boot·rabbitmq·java-rabbitmq
曼岛_12 小时前
[Java实战]Spring Boot 整合 Redis(十八)
java·spring boot·redis