likes
comments
collection
share

Dubbo3和Spring Boot整合过程

作者站长头像
站长
· 阅读数 30

前言

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。

<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,它主要做的事:

  • 注入 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<String> packagesToScan) {
        // 获取扫描的包路径
        // 注入 ServiceBean 后置处理器
        return new ServiceAnnotationPostProcessor(packagesToScan);
    }
}

ServiceAnnotationPostProcessor

Dubbo 对外提供的服务,在 Spring 里面被封装为org.apache.dubbo.config.spring.ServiceBeanDubbo3和Spring Boot整合过程 它继承自 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依赖的注入。 Dubbo3和Spring Boot整合过程 它是 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

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

转载自:https://juejin.cn/post/7288533395260358696
评论
请登录