Spring 扩展点实践:整合 Apache Dubbo(二)

Spring 扩展点实践:整合 Apache Dubbo(二)

Spring 扩展点实践:整合 Apache Dubbo(一) 中,D瓜哥介绍了 Dubbo 如何使用 Spring 的插件机制与 Spring 整合。限于篇幅原因,上一篇文章只介绍到了服务提供者的注册。本篇文章继续上一篇文章的主题,继续介绍 Spring 与 Dubbo 的整合过程。先来讲解一下服务消费者的生成过程。

Dubbo 生成服务消费者的过程

先来看看 XML 配置文件:

dubbo-demo/dubbo-demo-xml/dubbo-demo-xml-consumer/src/main/resources/spring/dubbo-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://dubbo.apache.org/schema/dubbo"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://dubbo.apache.org/schema/dubbo
                           http://dubbo.apache.org/schema/dubbo/dubbo.xsd">

    <dubbo:application name="demo-consumer"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>

</beans>

我们先看一下 ReferenceBean 类的声明:

org.apache.dubbo.config.spring.ReferenceBean
public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
        ApplicationContextAware, InitializingBean, DisposableBean {

    // 此处省略 N 行代码

    @Override
    public Object getObject() {
        return get();
    }

    // 此处省略 N 行代码

    @Override
    @SuppressWarnings({"unchecked"})
    public void afterPropertiesSet() throws Exception {

        // Initializes Dubbo's Config Beans before @Reference bean autowiring
        prepareDubboConfigBeans();

        // lazy init by default.
        if (init == null) {
            init = false;
        }

        // eager init if necessary.
        if (shouldInit()) {
            getObject();
        }
    }

    // 此处省略 N 行代码
}

这个类实现了 FactoryBean 接口,D瓜哥在 Spring 扩展点概览及实践:FactoryBean 中对 FactoryBean 介绍。所以,请在上面的 getObject() 打个断点。

另外,这个类还实现了 InitializingBean,D瓜哥在 Spring Bean 生命周期概述 中介绍了这个接口的用途。不了解的,请移步。

启动服务消费者程序,开始调试代码。跳过上文结束的配置解析阶段,进入到 org.apache.dubbo.config.bootstrap.DubboBootstrap#start 方法中。在这里,它调用了内部私有方法 referServices()。但是,这个方法其实啥也没做。

上面提到,ReferenceBean 实现了 FactoryBean 接口,那么直接在 org.apache.dubbo.config.spring.ReferenceBean#getObject 方法上打断点。当调用 applicationContext.getBean(XXX) 时,就会触发断点,一路跟下去就会发现,现在 org.apache.dubbo.config.ReferenceConfig#init 方法中完成各种初始化准备工作,然后调用 org.apache.dubbo.config.ReferenceConfig#createProxy 方法创建代理。而实际代理的创建工作是由 org.apache.dubbo.rpc.proxy.AbstractProxyFactory#getProxy(Invoker<T>, boolean) 方法创建的。这样说,也不算准确。因为 AbstractProxyFactory 对象是一个子类对象,子类是通过 Dubbo 的类 SPI 加载机制来动态选择创建的。

其实,Dubbo 服务消费者实例只是一个代理,通过代理封装统一的网络请求,实现 RPC 的调用过程。

Dubbo 注解集成简述

使用 Dubbo 注解集成的入口是 org.apache.dubbo.config.spring.context.annotation.EnableDubbo,直接上代码:

org.apache.dubbo.config.spring.context.annotation.EnableDubbo

/
 * Enables Dubbo components as Spring Beans, equals
 * {@link DubboComponentScan} and {@link EnableDubboConfig} combination.
 * <p>
 * Note : {@link EnableDubbo} must base on Spring Framework 4.2 and above
 *
 * @see DubboComponentScan
 * @see EnableDubboConfig
 * @since 2.5.8
 */
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {

    /
     * Base packages to scan for annotated @Service classes.
     * <p>
     * Use {@link #scanBasePackageClasses()} for a type-safe alternative to String-based
     * package names.
     *
     * @return the base packages to scan
     * @see DubboComponentScan#basePackages()
     */
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackages")
    String[] scanBasePackages() default {};

    /
     * Type-safe alternative to {@link #scanBasePackages()} for specifying the packages to
     * scan for annotated @Service classes. The package of each class specified will be
     * scanned.
     *
     * @return classes from the base packages to scan
     * @see DubboComponentScan#basePackageClasses
     */
    @AliasFor(annotation = DubboComponentScan.class, attribute = "basePackageClasses")
    Class<?>[] scanBasePackageClasses() default {};


    /
     * It indicates whether {@link AbstractConfig} binding to multiple Spring Beans.
     *
     * @return the default value is <code>true</code>
     * @see EnableDubboConfig#multiple()
     */
    @AliasFor(annotation = EnableDubboConfig.class, attribute = "multiple")
    boolean multipleConfig() default true;

}

这个注解非常重要。一共有两点需要注意。这个方法就是注解的三个属性,分别给出了三个最重要的参数:

  1. scanBasePackages — 定义了基础扫描的包。通过 @AliasFor 注解表明,这是定义 @DubboComponentScan 注解的 basePackages 属性。

  2. scanBasePackageClasses — 定义扫描的基础类。通过 @AliasFor 注解表明,这是定义 @DubboComponentScan 注解的 basePackageClasses 属性。

  3. multipleConfig — 可以将 AbstractConfig(上一篇文章 Spring 扩展点实践:整合 Apache Dubbo(一) 已经做过说明) 向 Spring 中多次注册。换句话说,你可以配置多个注册中心,配置多个监控中心等等。通过 @AliasFor 注解表明,这是定义 @EnableDubboConfig 注解的 multiple 属性,默认为 true

接下来,让我们看看非常重要的两点内容。

@EnableDubboConfig

@EnableDubbo 注解上面加了 @EnableDubboConfig 注解,我们来看一下它的源码:

org.apache.dubbo.config.spring.context.annotation.EnableDubboConfig
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import(DubboConfigConfigurationRegistrar.class)
public @interface EnableDubboConfig {

    /**
     * It indicates whether binding to multiple Spring Beans.
     *
     * @return the default value is <code>true</code>
     * @revised 2.5.9
     */
    boolean multiple() default true;

}

这里,我们看到了熟悉的 @ImportDubboConfigConfigurationRegistrar 从名字就能看出应该是实现了 ImportBeanDefinitionRegistrar 接口的,打开代码,果然如此。更

Spring 扩展点概览及实践Spring 扩展点实践:整合 MyBATIS 中有针对 @ImportImportBeanDefinitionRegistrar 的详细介绍。尤其是 MyBATIS 就是使用 ImportBeanDefinitionRegistrar 来做扩展的。不懂的,请移步。

关于 DubboConfigConfigurationRegistrar 的功能,这里做个简要总结:

  1. 使用 @EnableConfigurationBeanBindings 注解,将配置项和对一个的 Bean 类型做一个绑定。如果 multiple 属性为 true,则指出多次注册。

  2. 调用 org.apache.dubbo.config.spring.util.DubboBeanUtils#registerCommonBeans 方法,将公共的 Bean 注册到 Spring 中。这部分内容在 Spring 扩展点实践:整合 Apache Dubbo(一):registerCommonBeans 中已经给出了详细介绍,就不再赘述。

@DubboComponentScan

@EnableDubbo 注解上面加了 @DubboComponentScan 注解,直接上代码:

org.apache.dubbo.config.spring.context.annotation.DubboComponentScan
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {

    /
     * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
     * declarations e.g.: {@code @DubboComponentScan("org.my.pkg")} instead of
     * {@code @DubboComponentScan(basePackages="org.my.pkg")}.
     
     * @return the base packages to scan
     */
    String[] value() default {};

    /
     * Base packages to scan for annotated @Service classes. {@link #value()} is an
     * alias for (and mutually exclusive with) this attribute.
     * <p>
     * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
     * package names.
     *
     * @return the base packages to scan
     */
    String[] basePackages() default {};

    /*
     * Type-safe alternative to {@link #basePackages()} for specifying the packages to
     * scan for annotated @Service classes. The package of each class specified will be
     * scanned.
     *
     * @return classes from the base packages to scan
     */
    Class<?>[] basePackageClasses() default {};

}

又双叒叕看到了 @Import;又双叒叕看到了 Registrar,只是这次名字叫 DubboComponentScanRegistrar。跟上面的一样,不再赘述。

这里总结一下 DubboComponentScanRegistrar 的功能:注册了一个类为 ServiceAnnotationBeanPostProcessorBeanDefinition,将配置项的配置信息传递给这个 BeanDefinition 实例。 ServiceAnnotationBeanPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口,会在 Spring 的启动过程中,通过调用 postProcessBeanDefinitionRegistry 方法来注册相关的 BeanDefinition。关于这部分内容,请移步: Spring AOP 处理流程概述

在 Spring 启动过程中,就会调用 ServiceAnnotationBeanPostProcessorpostProcessBeanDefinitionRegistry 方法,在这个方法中,通过创建 DubboClassPathBeanDefinitionScanner (继承了 ClassPathBeanDefinitionScanner 类)实例,调用 scanner.scan(packageToScan) 来注册 BeanDefinition。另外,有一点需要指出的是: ServiceAnnotationBeanPostProcessor 目前是 @Deprecated,后续推荐使用 ServiceClassPostProcessor,而 ServiceAnnotationBeanPostProcessor 就是 ServiceClassPostProcessor 的子类。所以,目前处理逻辑都集中在了 ServiceClassPostProcessor 中。

关于 Apache Dubbo 与 Spring 的整合原理就全部介绍完毕了。如有什么问题,欢迎留言讨论。以后有时间,写写分布式事务解决方案 Seata 的一些原理。