java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Dubbo3和Spring Boot整合

Dubbo3和Spring Boot整合过程源码解析

作者:程序员小潘

Dubbo首先是提供了一个单独的模块来和Spring Boot做整合,利用 Spring Boot自动装配的功能,配置了一堆自动装配的组件,本文介绍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个子模块:

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.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>3.1.10</version>
</dependency>

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

入口模块

dubbo-spring-boot-starter 模块没有代码,只有一个 pom.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 模块:

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.dubbo</groupId>
    <artifactId>dubbo-spring-boot-autoconfigure-compatible</artifactId>
    <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 引用服务

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<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);
    }
}

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<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();
}

DubboDeployApplicationListener

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

ReferenceAnnotationBeanPostProcessor

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

依赖注入的对象被封装为 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 方法时再去调用底层的服务引用方法。

到此这篇关于Dubbo3和Spring Boot整合过程的文章就介绍到这了,更多相关Dubbo3和Spring Boot整合内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文