Dubbo学习之新编程模型

本文介绍Dubbo微服务框架中的注解驱动编程模型及外部化配置功能,重点讲解@DubboComponentScan和@EnableDubboConfig的使用方法与注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

整体愿景

随着微服务架构的广泛地推广和实施。在 Java 生态系统中,以 Spring Boot 和 Spring Cloud 为代表的微服务框架,引入了全新的编程模型,包括注解驱动(Annotation-Driven)、外部化配置(External Configuration)以及自动装配(Auto-Configure)等。新的编程模型无需 XML 配置、简化部署、提升开发效率。

为了更好地实践微服务架构,Dubbo 从 2.5.7 版本开始, 针对 Spring 应用场景(包括 Spring Boot 和 Spring Cloud),新引入注解驱动(Annotation-Driven)、外部化配置(External Configuration)等编程模型。同时,新的编程模型也是即将发布的 Spring Boot Starter(dubbo-spring-boot-starter) 的基础设施。更为重要的是,从 Dubbo 2.5.8 开始,无论传统 Spring 应用,还是 Spring Boot 应用,两者之间可以实现无缝迁移(无需任何调整)。

Dubbo 注解驱动(Annotation-Driven)

主目录

注解驱动(Annotation-Driven)

@DubboComponentScan

起始版本: 2.5.7
<dubbo:annotation>历史遗留问题
1. 注解支持不充分

在 Dubbo 2.5.7之前的版本 ,Dubbo 提供了两个核心注解 @Service 以及 @Reference,分别用于Dubbo 服务提供和 Dubbo 服务引用。其中,@Service 作为 XML 元素 <dubbo:service>的替代注解,与 Spring Framework @org.springframework.stereotype.Service 类似,用于服务提供方 Dubbo 服务暴露。与之相对应的@Reference,则是替代<dubbo:reference 元素,类似于 Spring 中的 @Autowired。

2.5.7 之前的Dubbo,与早期的 Spring Framework 2.5 存在类似的不足,即注解支持不够充分。注解需要和 XML 配置文件配合使用,如下所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/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-2.5.xsd
	http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">

    <dubbo:application name="annotation-provider"/>
    <dubbo:registry address="127.0.0.1:4548"/>
    <dubbo:annotation package="com.alibaba.dubbo.config.spring.annotation.provider"/>

</beans>
2. @Service Bean 不支持 Spring AOP

同时,使用 <dubbo:annotation> 方式扫描后的Dubbo @Service ,在 Spring 代理方面存在问题,如 GitHub 上的 issue https://github.com/alibaba/dubbo/issues/794:
关于dubbo @Service注解生成ServiceBean时, interface获取成spring 的代理对象的bug

在项目里, 我使用了

@Service
@Transactional
@com.alibaba.dubbo.config.annotation.Service
public class SUserJpushServiceImp

的形式, 来暴露服务。但是在发布服务的时候, interface class 是通过serviceConfig.setInterface(bean.getClass().getInterfaces()[0]); 的形式获取, 刚好, 我的service都使用了@Transactional注解, 对象被代理了。所以获取到的interface是Spring的代理接口...

不少热心的小伙伴不仅发现这个历史遗留问题,而且提出了一些修复方案。同时,为了更好地适配 Spring 生命周期以及将 Dubbo 完全向注解驱动编程模型过渡,因此,引入了全新 Dubbo 组件扫描注解 - @DubboComponentScan

注: <dubbo:annotation> Spring AOP 问题将在 2.5.9 中修复:https://github.com/alibaba/dubbo/issues/1125
3. @Reference 不支持字段继承性

假设有一个 Spring Bean AnnotationAction 直接通过字段annotationService 标记 @Reference 引用 AnnotationService :

package com.alibaba.dubbo.examples.annotation.action;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.examples.annotation.api.AnnotationService;
import org.springframework.stereotype.Component;


@Component("annotationAction")
public class AnnotationAction {

    @Reference
    private AnnotationService annotationService;

    public String doSayHello(String name) {
        return annotationService.sayHello(name);
    }

}

AnnotationAction 被 XML 元素 <dubbo:annotation> 扫描后:

<dubbo:annotation package="com.alibaba.dubbo.examples.annotation.action"/>

字段 annotationService 能够引用到 AnnotationService,执行 doSayHello 方法能够正常返回。

如果将字段annotationService 抽取到AnnotationAction 的父类BaseAction 后,AnnotationService 无法再被引用,改造如下所示:

AnnotationAction.java

@Component("annotationAction")
public class AnnotationAction extends BaseAction {

    public String doSayHello(String name) {
        return getAnnotationService().sayHello(name);
    }

}

BaseAction.java

public abstract class BaseAction {

    @Reference
    private AnnotationService annotationService;

    protected AnnotationService getAnnotationService() {
        return annotationService;
    }
}

改造后,再次执行 doSayHello 方法,NullPointerException 将会被抛出。说明<dubbo:annotation> 并不支持@Reference 字段继承性。

了解了历史问题,集合整体愿景,下面介绍@DubboComponentScan 的设计原则。

设计原则

Spring Framework 3.1 引入了新 Annotation - @ComponentScan , 完全替代了 XML 元素 <context:component-scan> 。同样, @DubboComponentScan 作为 Dubbo 2.5.7 新增的 Annotation,也是XML 元素 <dubbo:annotation> 的替代方案。

在命名上(类名以及属性方法),为了简化使用和关联记忆,Dubbo 组件扫描 Annotation @DubboComponentScan,借鉴了 Spring Boot 1.3 引入的 @ServletComponentScan。定义如下:

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

}

注意:basePackages() 和 value() 均能支持占位符(placeholder)指定的包名

在职责上,@DubboComponentScan 相对于 Spring Boot @ServletComponentScan 更为繁重,原因在于处理 Dubbo @Service 类暴露 Dubbo 服务外,还有帮助 Spring Bean @Reference字段或者方法注入 Dubbo 服务代理。

在场景上,Spring Framework @ComponentScan 组件扫描逻辑更为复杂。而在 @DubboComponentScan 只需关注 @Service 和 @Reference 处理。

在功能上, @DubboComponentScan 不但需要提供完整 Spring AOP 支持的能力,而且还得具备@Reference 字段可继承性的能力。

了解基本设计原则后,下面通过完整的示例,简介@DubboComponentScan 使用方法以及注意事项。

使用方法

后续通过服务提供方(@Serivce)以及服务消费方(@Reference)两部分来介绍@DubboComponentScan 使用方法。

假设,服务提供方和服务消费方均依赖服务接口DemoService:

package com.alibaba.dubbo.demo;

public interface DemoService {

    String sayHello(String name);

}
服务提供方(@Serivce
实现 DemoService

服务提供方实现DemoService - AnnotationDemoService ,同时标注 Dubbo @Service :

package com.alibaba.dubbo.demo.provider;

import com.alibaba.dubbo.config.annotation.Service;
import com.alibaba.dubbo.demo.DemoService;

/**
 * Annotation {@link DemoService} 实现
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
@Service
public class AnnotationDemoService implements DemoService {

    @Override
    public String sayHello(String name) {
        return "Hello , " + name;
    }

}
服务提供方 Annotation 配置

将 AnnotationDemoService 暴露成Dubbo 服务,需要依赖 Spring Bean:AplicationConfigProtocolConfig 以及 RegistryConfig 。这三个 Spring Bean 过去可通过 XML 文件方式组装 Spring Bean:

<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:dubbo="http://code.alibabatech.com/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-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    ">

    <!-- 当前应用信息配置 -->
    <dubbo:application name="dubbo-annotation-provider"/>

    <!-- 连接注册中心配置 -->
    <dubbo:registry id="my-registry" address="N/A"/>

    <dubbo:protocol name="dubbo" port="12345"/>

</beans>

以上装配方式不予推荐,推荐使用 Annotation 配置,因此可以换成 Spring @Configuration Bean 的形式:

package com.alibaba.dubbo.demo.config;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.ProtocolConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 服务提供方配置
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 扫描 Dubbo 组件
public class ProviderConfiguration {

    /**
     * 当前应用配置
     */
    @Bean("dubbo-annotation-provider")
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-provider");
        return applicationConfig;
    }

    /**
     * 当前连接注册中心配置
     */
    @Bean("my-registry")
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
        return registryConfig;
    }

    /**
     * 当前连接注册中心配置
     */
    @Bean("dubbo")
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setPort(12345);
        return protocolConfig;
    }
}
服务提供方引导类
package com.alibaba.dubbo.demo.bootstrap;

import com.alibaba.dubbo.demo.DemoService;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 服务提供方引导类
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public class ProviderBootstrap {

    public static void main(String[] args) {
        // 创建 Annotation 配置上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册配置 Bean
        context.register(ProviderConfiguration.class);
        // 启动上下文
        context.refresh();
        // 获取 DemoService Bean
        DemoService demoService = context.getBean(DemoService.class);
        // 执行 sayHello 方法
        String message = demoService.sayHello("World");
        // 控制台输出信息
        System.out.println(message);
    }
    
}

ProviderBootstrap 启动并执行后,控制输出与预期一致:

Hello , World

以上直接结果说明 @DubboComponentScan("com.alibaba.dubbo.demo.provider") 扫描后,标注 Dubbo @Service 的 AnnotationDemoService 被注册成 Spring Bean,可从 Spring ApplicationContext 自由获取。

服务消费方(@Reference
服务 DemoService
package com.alibaba.dubbo.demo.consumer;

import com.alibaba.dubbo.config.annotation.Reference;
import com.alibaba.dubbo.demo.DemoService;

/**
 * Annotation 驱动 {@link DemoService} 消费方
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public class AnnotationDemoServiceConsumer {

    @Reference(url = "dubbo://127.0.0.1:12345")
    private DemoService demoService;

    public String doSayHell(String name) {
        return demoService.sayHello(name);
    }
}
服务消费方 Annotation 配置

与服务提供方配置类似,服务消费方也许 Dubbo 相关配置 Bean - ConsumerConfiguration

package com.alibaba.dubbo.demo.config;

import com.alibaba.dubbo.config.ApplicationConfig;
import com.alibaba.dubbo.config.RegistryConfig;
import com.alibaba.dubbo.config.spring.context.annotation.DubboComponentScan;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 服务消费方配置
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
@Configuration
@DubboComponentScan
public class ConsumerConfiguration {

    /**
     * 当前应用配置
     */
    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-consumer");
        return applicationConfig;
    }

    /**
     * 当前连接注册中心配置
     */
    @Bean
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setAddress("N/A");
        return registryConfig;
    }

    /**
     * 注册 AnnotationDemoServiceConsumer,@DubboComponentScan 将处理其中 @Reference 字段。
     * 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的话,
     * 即使 @DubboComponentScan 指定 package 也不会进行处理,与 Spring @Autowired 同理
     */
    @Bean
    public AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {
        return new AnnotationDemoServiceConsumer();
    }

}
服务消费方引导类

服务消费方需要先引导服务提供方,下面的实例将会启动两个 Spring 应用上下文,首先引导服务提供方 Spring 应用上下文,同时,需要复用前面Annotation 配置 ProviderConfiguration

/**
     * 启动服务提供方上下文
     */
    private static void startProviderContext() {
        // 创建 Annotation 配置上下文
        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
        // 注册配置 Bean
        providerContext.register(ProviderConfiguration.class);
        // 启动服务提供方上下文
        providerContext.refresh();
    }

然后引导服务消费方Spring 应用上下文:

  /**
     * 启动并且返回服务消费方上下文
     *
     * @return AnnotationConfigApplicationContext
     */
    private static ApplicationContext startConsumerContext() {
        // 创建服务消费方 Annotation 配置上下文
        AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
        // 注册服务消费方配置 Bean
        consumerContext.register(ConsumerConfiguration.class);
        // 启动服务消费方上下文
        consumerContext.refresh();
        // 返回服务消费方 Annotation 配置上下文
        return consumerContext;
    }

完整的引导类实现:

package com.alibaba.dubbo.demo.bootstrap;

import com.alibaba.dubbo.demo.config.ConsumerConfiguration;
import com.alibaba.dubbo.demo.config.ProviderConfiguration;
import com.alibaba.dubbo.demo.consumer.AnnotationDemoServiceConsumer;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * 服务消费端引导类
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public class ConsumerBootstrap {

    public static void main(String[] args) {
        // 启动服务提供方上下文
        startProviderContext();
        // 启动并且返回服务消费方上下文
        ApplicationContext consumerContext = startConsumerContext();
        // 获取 AnnotationDemoServiceConsumer Bean
        AnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);
        // 执行 doSayHello 方法
        String message = consumer.doSayHello("World");
        // 输出执行结果
        System.out.println(message);
    }

    /**
     * 启动并且返回服务消费方上下文
     *
     * @return AnnotationConfigApplicationContext
     */
    private static ApplicationContext startConsumerContext() {
        // 创建服务消费方 Annotation 配置上下文
        AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
        // 注册服务消费方配置 Bean
        consumerContext.register(ConsumerConfiguration.class);
        // 启动服务消费方上下文
        consumerContext.refresh();
        // 返回服务消费方 Annotation 配置上下文
        return consumerContext;
    }

    /**
     * 启动服务提供方上下文
     */
    private static void startProviderContext() {
        // 创建 Annotation 配置上下文
        AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();
        // 注册配置 Bean
        providerContext.register(ProviderConfiguration.class);
        // 启动服务提供方上下文
        providerContext.refresh();
    }

}

运行ConsumerBootstrap结果,仍然符合期望,AnnotationDemoServiceConsumer 输出:

Hello , World
Spring AOP 支持

前面提到 <dubbo:annotation> 注册 Dubbo @Service 组件后,在 Spring AOP 支持方面存在问题。事务作为 Spring AOP 的功能扩展,自然也会在 <dubbo:annotation>中不支持。

@DubboComponentScan 针对以上问题,实现了对 Spring AOP 是完全兼容。将上述服务提供方 Annotation 配置做出一定的调整,标注@EnableTransactionManagement 以及自定义实现PlatformTransactionManager :

@Configuration
@DubboComponentScan("com.alibaba.dubbo.demo.provider") // 扫描 Dubbo 组件
@EnableTransactionManagement // 激活事务管理
public class ProviderConfiguration {
  // 省略其他配置 Bean 定义
  
    /**
     * 自定义事务管理器
     */
    @Bean
    @Primary
    public PlatformTransactionManager transactionManager() {
        return new PlatformTransactionManager() {

            @Override
            public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {
                System.out.println("get transaction ...");
                return new SimpleTransactionStatus();
            }

            @Override
            public void commit(TransactionStatus status) throws TransactionException {
                System.out.println("commit transaction ...");
            }

            @Override
            public void rollback(TransactionStatus status) throws TransactionException {
                System.out.println("rollback transaction ...");
            }
        };
    }
}

同时调整 AnnotationDemoService - 增加@Transactional 注解:

@Service
@Transactional
public class AnnotationDemoService implements DemoService {
	// 省略实现,保持不变
}

再次运行ConsumerBootstrap , 观察控制台输出内容:

get transaction ...
commit transaction ...
Hello , World

输入内容中多处了两行,说明自定义 PlatformTransactionManager getTransaction(TransactionDefinition) 以及 commit(TransactionStatus) 方法被执行,进而说明 AnnotationDemoService 的sayHello(String) 方法执行时,事务也伴随执行。

注意事项

ConsumerConfiguration 上的 @DubboComponentScan 并没有指定 basePackages 扫描,这种情况会将ConsumerConfiguration 当做 basePackageClasses ,即扫描ConsumerConfiguration 所属的 package com.alibaba.dubbo.demo.config 以及子 package。由于当前示例中,不存在标注 Dubbo @Service的类,因此在运行时日志(如果开启的话)会输出警告信息:

WARN :  [DUBBO] No Spring Bean annotating Dubbo's @Service was found in Spring BeanFactory, dubbo version: 2.0.0, current host: 127.0.0.1

以上信息大可不必担忧,因为 @DubboComponentScan 除了扫描 Dubbo @Service 组件以外,还将处理 @Reference字段注入。然而读者特别关注@Reference字段注入的规则。

以上实现为例,AnnotationDemoServiceConsumer 必须申明为 Spring @Bean 或者 @Component(或者其派生注解),否则 @DubboComponentScan 不会主动将标注 @Reference字段所在的声明类提成为 Spring Bean,换句话说,如果 @Reference字段所在的声明类不是 Spring Bean 的话, @DubboComponentScan 不会处理@Reference注入,其原理与 Spring @Autowired 一致。

以上使用不当可能会导致相关问题,如 GitHub 上曾有小伙伴提问:https://github.com/alibaba/dubbo/issues/825

li362692680 提问:

@DubboComponentScan注解在消费端扫描包时扫描的是 @Service注解??不是@Reference注解?? 启动时报 DubboComponentScanRegistrar-85]-[main]-[INFO] 0 annotated @Service Components { [] }

笔者(mercyblitz)回复:

@Reference 类似于 @Autowired 一样,首先其申明的类必须被 Spring 上下文当做一个Bean,因此,Dubbo 并没有直接将 @Reference 字段所在的类提升成 Bean。

综上所述,这并不是一个问题,而是用法不当!

已知问题

最新发布的 Dubbo 2.5.8 中,@DubboComponentScan 在以下特殊场景下存在 Spring @Service 不兼容情况:

假设有两个服务实现类 A 和 B,同时存放在com.acme 包下:

  • A 标注 Dubbo @Service
  • B 标注 Dubbo @Service 和 Spring @Service

当 Spring @ComponentScan 先扫描com.acme 包时,B 被当做 Spring Bean 的候选类。随后,@DubboComponentScan 也扫描相同的包。当应用启动时,A 和 B 虽然都是 Spring Bean,可仅 A 能够暴露 Dubbo 服务,B 则丢失。

问题版本:2.5.72.5.8

问题详情:https://github.com/alibaba/dubbo/issues/1120

修复版本:2.5.9(下个版本)



Dubbo 外部化配置(Externalized Configuration)

主目录

外部化配置(External Configuration)

Dubbo 注解驱动例子中,无论是服务提供方,还是服务消费方,均需要转配相关配置Bean:

    @Bean
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-consumer");
        return applicationConfig;
    }

虽然实现类似于ProviderConfiguration 和 ConsumerConfiguration 这样的 Spring @Configuration Bean 成本并不高,不过通过 Java Code 的方式定义配置 Bean,或多或少是一种 Hard Code(硬编码)的行为,缺少弹性。

尽管在 Spring 应用中,可以通过 @Value 或者 Environment 的方式获取外部配置,其代码简洁性以及类型转换灵活性存在明显的不足。因此,Spring Boot 提出了外部化配置(External Configuration)的感念,即通过程序以外的配置源,动态地绑定指定类型。

随着 Spring Boot / Spring Cloud 应用的流行,开发人员逐渐地接受并且使用 Spring Boot 外部化配置(External Configuration),即通过 application.properties 或者 bootstrap.properties 装配配置 Bean。

下列表格记录了 Dubbo 内置配置类:

配置类标签用途解释
ProtocolConfig<dubbo:protocol/>协议配置用于配置提供服务的协议信息,协议由提供方指定,消费方被动接受
ApplicationConfig<dubbo:application/>应用配置用于配置当前应用信息,不管该应用是提供者还是消费者
ModuleConfig<dubbo:module/>模块配置用于配置当前模块信息,可选
RegistryConfig<dubbo:registry/>注册中心配置用于配置连接注册中心相关信息
MonitorConfig<dubbo:monitor/>监控中心配置用于配置连接监控中心相关信息,可选
ProviderConfig<dubbo:provider/>提供方配置当 ProtocolConfig 和 ServiceConfig 某属性没有配置时,采用此缺省值,可选
ConsumerConfig<dubbo:consumer/>消费方配置当 ReferenceConfig 某属性没有配置时,采用此缺省值,可选
MethodConfig<dubbo:method/>方法配置用于 ServiceConfig 和 ReferenceConfig 指定方法级的配置信息
ArgumentConfig<dubbo:argument/>参数配置用于指定方法参数配置

通过申明对应的 Spring 扩展标签,在 Spring 应用上下文中将自动生成相应的配置 Bean。

在 Dubbo 官方用户手册的“属性配置”章节中,dubbo.properties 配置属性能够映射到 ApplicationConfig 、ProtocolConfig以及 RegistryConfig 的字段。从某种意义上来说,dubbo.properties 也是 Dubbo 的外部化配置。

其中,引用“映射规则”的内容:

映射规则

将 XML 配置的标签名,加属性名,用点分隔,多个属性拆成多行

  • 比如:dubbo.application.name=foo等价于<dubbo:application name="foo" />
  • 比如:dubbo.registry.address=10.20.153.10:9090等价于<dubbo:registryaddress="10.20.153.10:9090" />

如果 XML 有多行同名标签配置,可用 id 号区分,如果没有 id 号将对所有同名标签生效

  • 比如:dubbo.protocol.rmi.port=1234等价于<dubbo:protocol id="rmi" name="rmi" port="1099" />2
  • 比如:dubbo.registry.china.address=10.20.153.10:9090等价于<dubbo:registry id="china"address="10.20.153.10:9090" />

下面是 dubbo.properties 的一个典型配置:

dubbo.application.name=foo
dubbo.application.owner=bar
dubbo.registry.address=10.20.153.10:9090

根据“映射规则”,Dubbo 即支持单配置 Bean 映射,也支持多 Bean 映射。综合以上需求,既要兼容 Dubbo 已有的一个或多个 Bean 字段映射绑定,也支持外部化配置。

特别提醒:外部化配置(External Configuration)并非 Spring Boot 特有,即使在 Spring Framework 场景下亦能支持。也就是说 Dubbo 外部化配置即可在 Spring Framework 中工作,也能在 Spring Boot 中运行。

Dubbo 外部化配置(External Configuration) 支持起始版本为:2.5.8

@EnableDubboConfig

起始版本:2.5.8
使用说明
@EnableDubboConfig 定义
public @interface EnableDubboConfig {

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

}
  • multiple : 表示是否支持多Dubbo 配置 Bean 绑定。默认值为 false ,即单 Dubbo 配置 Bean 绑定
单 Dubbo 配置 Bean 绑定

为了更好地向下兼容,@EnableDubboConfig 提供外部化配置属性与 Dubbo 配置类之间的绑定,其中映射关系如下:

配置类外部化配置属性前缀用途
ProtocolConfigdubbo.protocol协议配置
ApplicationConfigdubbo.application应用配置
ModuleConfigdubbo.module模块配置
RegistryConfigdubbo.registry注册中心配置
MonitorConfigdubbo.monitor监控中心配置
ProviderConfigdubbo.provider提供方配置
ConsumerConfigdubbo.consumer消费方配置

当标注 @EnableDubboConfig 的类被扫描注册后,同时 Spring(Spring Boot)应用配置(PropertySources)中存在dubbo.application.* 时,ApplicationConfig Bean 将被注册到在 Spring 上下文。否则,不会被注册。如果出现dubbo.registry.*的配置,那么,RegistryConfig Bean 将会创建,以此类推。即按需装配 Dubbo 配置 Bean。

如果需要指定配置 Bean的 id,可通过**.id 属性设置,以dubbo.application 为例:

## application
dubbo.application.id = applicationBean
dubbo.application.name = dubbo-demo-application

以上配置等同于以下 Java Config Bean:

    @Bean("applicationBean")
    public ApplicationConfig applicationBean() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-demo-application");
        return applicationConfig;
    }

大致上配置属性与配置类绑定模式 - dubbo.application.* 映射到 ApplicationConfig 中的字段。

注:当配置属性名称无法在配置类中找到字段时,将会忽略绑定

多 Dubbo 配置 Bean 绑定

Dubbo @Service 和 @Reference 允许 Dubbo 应用关联ApplicationConfig Bean 或者指定多个RegistryConfig Bean 等能力。换句话说,Dubbo 应用上下文中可能存在多个ApplicationConfig 等 Bean定义。

为了适应以上需要,因此从Dubbo 2.5.9 开始,@EnableDubboConfig 支持多 Dubbo 配置 Bean 绑定,同时按照业界规约标准,与单 Dubbo 配置 Bean 绑定约定不同,配置属性前缀均为英文复数形式:

详情请参考 :https://github.com/alibaba/dubbo/issues/1141

  • dubbo.applications
  • dubbo.modules
  • dubbo.registries
  • dubbo.protocols
  • dubbo.monitors
  • dubbo.providers
  • dubbo.consumers

dubbo.applications 为例,基本的模式如下:

dubbo.applications.${bean-name}.property-name = ${property-value}

请读者注意,在单 Dubbo 配置 Bean 绑定时,可以通过指定id 属性的方式,定义ApplicationConfig Bean 的ID,即dubbo.application.id

而在多 Dubbo 配置 Bean 绑定时,Bean ID 则由dubbo.applications.与属性字段名称(.property-name)之间的字符来表达。

如下配置:

# multiple Bean definition
dubbo.applications.applicationBean.name = dubbo-demo-application
dubbo.applications.applicationBean2.name = dubbo-demo-application2
dubbo.applications.applicationBean3.name = dubbo-demo-application3

该配置内容中,绑定了三个ApplicationConfig Bean,分别是applicationBeanapplicationBean2以及applicationBean3

示例说明

@EnableDubboConfig 的使用方法很简答, 再次强调一点,当规约的外部配置存在时,相应的 Dubbo 配置类 才会提升为 Spring Bean。简言之,按需装配。

单 Dubbo 配置 Bean 绑定
外部化配置文件

将以下内容的外部化配置文件物理路径为:classpath:/META-INF/config.properties:

# 单 Dubbo 配置 Bean 绑定
## application
dubbo.application.id = applicationBean
dubbo.application.name = dubbo-demo-application

## module
dubbo.module.id = moduleBean
dubbo.module.name = dubbo-demo-module

## registry
dubbo.registry.address = zookeeper://192.168.99.100:32770

## protocol
dubbo.protocol.name = dubbo
dubbo.protocol.port = 20880

## monitor
dubbo.monitor.address = zookeeper://127.0.0.1:32770

## provider
dubbo.provider.host = 127.0.0.1

## consumer
dubbo.consumer.client = netty
@EnableDubboConfig 配置 Bean
/**
 * Dubbo 配置 Bean
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
@EnableDubboConfig
@PropertySource("META-INF/config.properties")
@Configuration
public class DubboConfiguration {

}
实现引导类
/**
 * Dubbo 配置引导类
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public class DubboConfigurationBootstrap {

    public static void main(String[] args) {
        // 创建配置上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册当前配置 Bean
        context.register(DubboConfiguration.class);
        context.refresh();
 	    // application
        ApplicationConfig applicationConfig = context.getBean("applicationBean", ApplicationConfig.class);
        System.out.printf("applicationBean.name = %s \n", applicationConfig.getName());

        // module
        ModuleConfig moduleConfig = context.getBean("moduleBean", ModuleConfig.class);
        System.out.printf("moduleBean.name = %s \n", moduleConfig.getName());

        // registry
        RegistryConfig registryConfig = context.getBean(RegistryConfig.class);
        System.out.printf("registryConfig.name = %s \n", registryConfig.getAddress());

        // protocol
        ProtocolConfig protocolConfig = context.getBean(ProtocolConfig.class);
        System.out.printf("protocolConfig.name = %s \n", protocolConfig.getName());
        System.out.printf("protocolConfig.port = %s \n", protocolConfig.getPort());

        // monitor
        MonitorConfig monitorConfig = context.getBean(MonitorConfig.class);
        System.out.printf("monitorConfig.name = %s \n", monitorConfig.getAddress());

        // provider
        ProviderConfig providerConfig = context.getBean(ProviderConfig.class);
        System.out.printf("providerConfig.name = %s \n", providerConfig.getHost());

        // consumer
        ConsumerConfig consumerConfig = context.getBean(ConsumerConfig.class);
        System.out.printf("consumerConfig.name = %s \n", consumerConfig.getClient());
    }
}
执行结果
applicationBean.name = dubbo-demo-application 
moduleBean.name = dubbo-demo-module 
registryConfig.name = zookeeper://192.168.99.100:32770 
protocolConfig.name = dubbo 
protocolConfig.port = 20880 
monitorConfig.name = zookeeper://127.0.0.1:32770 
providerConfig.name = 127.0.0.1 
consumerConfig.name = netty 

不难发现,@EnableDubboConfig 配置 Bean 配合外部化文件 classpath:/META-INF/config.properties,与执行输出内容相同。

多 Dubbo 配置 Bean 绑定
外部化配置文件

将以下内容的外部化配置文件物理路径为:classpath:/META-INF/multiple-config.properties:

# 多 Dubbo 配置 Bean 绑定
## dubbo.applications
dubbo.applications.applicationBean.name = dubbo-demo-application
dubbo.applications.applicationBean2.name = dubbo-demo-application2
dubbo.applications.applicationBean3.name = dubbo-demo-application3
@EnableDubboConfig 配置 Bean(多)
@EnableDubboConfig(multiple = true)
@PropertySource("META-INF/multiple-config.properties")
private static class DubboMultipleConfiguration {

}	
实现引导类
/**
 * Dubbo 配置引导类
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public class DubboConfigurationBootstrap {
    public static void main(String[] args) {
        // 创建配置上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册当前配置 Bean
        context.register(DubboMultipleConfiguration.class);
        context.refresh();

        // 获取 ApplicationConfig Bean:"applicationBean"、"applicationBean2" 和 "applicationBean3"
        ApplicationConfig applicationBean = context.getBean("applicationBean", ApplicationConfig.class);
        ApplicationConfig applicationBean2 = context.getBean("applicationBean2", ApplicationConfig.class);
        ApplicationConfig applicationBean3 = context.getBean("applicationBean3", ApplicationConfig.class);

        System.out.printf("applicationBean.name = %s \n", applicationBean.getName());
        System.out.printf("applicationBean2.name = %s \n", applicationBean2.getName());
        System.out.printf("applicationBean3.name = %s \n", applicationBean3.getName());
    }
}
执行结果
applicationBean.name = dubbo-demo-application 
applicationBean2.name = dubbo-demo-application2 
applicationBean3.name = dubbo-demo-application3 

@EnableDubboConfig(multiple = true) 执行后,运行结果说明ApplicationConfig Bean 以及 ID 的定义方式。

@EnableDubboConfigBinding & @EnableDubboConfigBindings

@EnableDubboConfig适合绝大多数外部化配置场景,然而无论是单 Bean 绑定,还是多 Bean 绑定,其外部化配置属性前缀是固化的,如dubbo.application 以及 dubbo.applications 。

当应用需要自定义外部化配置属性前缀@EnableDubboConfigBinding能提供更大的弹性,支持单个外部化配置属性前缀(prefix) 与 Dubbo 配置 Bean 类型(AbstractConfig 子类)绑定,如果需要多次绑定时,可使用@EnableDubboConfigBindings

尽管 Dubbo 推荐使用 Java 8 ,然而实际的情况,运行时的 JDK 的版本可能从 6到8 均有。因此,@EnableDubboConfigBinding 没有实现java.lang.annotation.Repeatable,即允许实现类不支持重复标注@EnableDubboConfigBinding

@EnableDubboConfigBinding 在支持外部化配置属性与 Dubbo 配置类绑定时,与 Dubbo 过去的映射行为不同,被绑定的 Dubbo 配置类将会提升为 Spring Bean,无需提前装配 Dubbo 配置类。同时,支持多 Dubbo 配置Bean 装配。其 Bean 的绑定规则与@EnableDubboConfig一致。

起始版本: 2.5.8
使用说明
@EnableDubboConfigBinding 定义
public @interface EnableDubboConfigBinding {

    /**
     * The name prefix of the properties that are valid to bind to {@link AbstractConfig Dubbo Config}.
     *
     * @return the name prefix of the properties to bind
     */
    String prefix();

    /**
     * @return The binding type of {@link AbstractConfig Dubbo Config}.
     * @see AbstractConfig
     * @see ApplicationConfig
     * @see ModuleConfig
     * @see RegistryConfig
     */
    Class<? extends AbstractConfig> type();

    /**
     * It indicates whether {@link #prefix()} binding to multiple Spring Beans.
     *
     * @return the default value is <code>false</code>
     */
    boolean multiple() default false;

}
  • prefix() : 指定待绑定 Dubbo 配置类的外部化配置属性的前缀,比如dubbo.application 为 ApplicationConfig 的外部化配置属性的前缀。prefix() 支持占位符(Placeholder), 并且其关联前缀值是否以"." 作为结尾字符是可选的,即prefix() = "dubbo.application" 与 prefix() = "dubbo.application." 效果相同
  • type() : 指定 Dubbo 配置类,所有 AbstractConfig 的实现子类即可,如ApplicationConfig 、RegistryConfig 以及 ProtocolConfig 等
  • multiple() : 表明是否需要将prefix() 作为多个 type() 类型的 Spring Bean 外部化配置属性。默认值为false,即默认支持单个类型的 Spring 配置 Bean

假设标注 @EnableDubboConfigBinding 的实现类被 Spring 应用上下文扫描并且注册后,其中prefix() = dubbo.app 、 type() = ApplicationConfig.class ,且外部配置内容为:

dubbo.app.id = applicationBean
dubbo.app.name = dubbo-demo-application

Spring 应用上下文启动后,一个 ID 为 "applicationBean" 的 ApplicationConfig Bean 被初始化,其 name 字段被设置为 "dubbo-demo-application"。

EnableDubboConfigBindings 定义
public @interface EnableDubboConfigBindings {

    /**
     * The value of {@link EnableDubboConfigBindings}
     *
     * @return non-null
     */
    EnableDubboConfigBinding[] value();

}
  • value : 指定多个EnableDubboConfigBinding,用于实现外部化配置属性前缀(prefix) 与 Dubbo 配置 Bean 类型(AbstractConfig 子类)绑定。
示例说明
外部化配置文件

将以下内容的外部化配置文件物理路径为:classpath:/META-INF/bindings.properties

# classpath:/META-INF/bindings.properties
## 占位符值 : ApplicationConfig 外部配置属性前缀
applications.prefix = dubbo.apps.

## 多 ApplicationConfig Bean 绑定
dubbo.apps.applicationBean.name = dubbo-demo-application
dubbo.apps.applicationBean2.name = dubbo-demo-application2
dubbo.apps.applicationBean3.name = dubbo-demo-application3

## 单 ModuleConfig Bean 绑定
dubbo.module.id = moduleBean
dubbo.module.name = dubbo-demo-module

## 单 RegistryConfig Bean 绑定
dubbo.registry.address = zookeeper://192.168.99.100:32770
EnableDubboConfigBindings 配置 Bean

DubboConfiguration 作为 Dubbo 配置 Bean,除通过 @EnableDubboConfigBinding 绑定之外,还需要 @PropertySource 指定外部化配置文件(classpath:/META-INF/bindings.properties):

/**
 * Dubbo 配置 Bean
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
@EnableDubboConfigBindings({
        @EnableDubboConfigBinding(prefix = "${applications.prefix}",
                type = ApplicationConfig.class, multiple = true), // 多 ApplicationConfig Bean 绑定
        @EnableDubboConfigBinding(prefix = "dubbo.module", // 不带 "." 后缀
                type = ModuleConfig.class), // 单 ModuleConfig Bean 绑定
        @EnableDubboConfigBinding(prefix = "dubbo.registry.", // 带 "." 后缀
                type = RegistryConfig.class) // 单 RegistryConfig Bean 绑定
})
@PropertySource("META-INF/bindings.properties")
@Configuration
public class DubboConfiguration {
  
}
实现引导类

通过之前的使用说明,当 EnableDubboConfigBinding 将外部配置化文件classpath:/META-INF/dubbo.properties 绑定到 ApplicationConfig后,其中 Spring Bean "applicationBean" 的 name 字段被设置成 "dubbo-demo-application"。同时, EnableDubboConfigBinding 所标注的 DubboConfiguration 需要被 Sring 应用上下文注册:

/**
 * Dubbo 配置引导类
 *
 * @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>
 */
public class DubboConfigurationBootstrap {

    public static void main(String[] args) {
        // 创建配置上下文
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册当前配置 Bean
        context.register(DubboConfiguration.class);
        context.refresh();
 		// 获取 ApplicationConfig Bean:"applicationBean"、"applicationBean2" 和 "applicationBean3"
        ApplicationConfig applicationBean = context.getBean("applicationBean", ApplicationConfig.class);
        ApplicationConfig applicationBean2 = context.getBean("applicationBean2", ApplicationConfig.class);
        ApplicationConfig applicationBean3 = context.getBean("applicationBean3", ApplicationConfig.class);

        System.out.printf("applicationBean.name = %s \n", applicationBean.getName());
        System.out.printf("applicationBean2.name = %s \n", applicationBean2.getName());
        System.out.printf("applicationBean3.name = %s \n", applicationBean3.getName());

        // 获取 ModuleConfig Bean:"moduleBean"
        ModuleConfig moduleBean = context.getBean("moduleBean", ModuleConfig.class);

        System.out.printf("moduleBean.name = %s \n", moduleBean.getName());

        // 获取 RegistryConfig Bean
        RegistryConfig registry = context.getBean(RegistryConfig.class);

        System.out.printf("registry.address = %s \n", registry.getAddress());
    }
}
运行结果

DubboConfigurationBootstrap 运行后控制台输出:

applicationBean.name = dubbo-demo-application 
applicationBean2.name = dubbo-demo-application2 
applicationBean3.name = dubbo-demo-application3 
moduleBean.name = dubbo-demo-module 
registry.address = zookeeper://192.168.99.100:32770 

输出的内容与classpath:/META-INF/bindings.properties 绑定的内容一致,符合期望


转载自:https://github.com/mercyblitz/blogs/tree/master/java/dubbo

更多文章:

https://github.com/apache/incubator-dubbo-spring-boot-project/tree/0.2.0/dubbo-spring-boot-autoconfigure

https://github.com/apache/incubator-dubbo-spring-boot-project/blob/0.2.0/README_CN.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值