前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >1.3.Spring Boot核心注解@DubboComponentScan

1.3.Spring Boot核心注解@DubboComponentScan

作者头像
itjim
修改2019-11-22 10:51:28
6.5K2
修改2019-11-22 10:51:28
举报
文章被收录于专栏:springboot解析springboot解析

通过 @EnableDubbo 可以在指定的包名下(通过 scanBasePackages),或者指定的类中(通过 scanBasePackageClasses)扫描 Dubbo 的服务提供者(以 @Service 标注)以及 Dubbo 的服务消费者(以 Reference 标注)。

扫描到 Dubbo 的服务提供方和消费者之后,对其做相应的组装并初始化,并最终完成服务暴露或者引用的工作。

当然,如果不使用外部化配置(External Configuration)的话,也可以直接使用 @DubboComponentScan。

在 Dubbo 中使用注解

随着微服务架构的广泛地推广和实施。在 Java 生态系统中,以 Spring Boot 和 Spring Cloud 为代表的微服务框架,引入了全新的编程模型,包括:

  • 注解驱动(Annotation-Driven)
  • 外部化配置(External Configuration)
  • 以及自动装配(Auto-Configure)

新的编程模型无需 XML 配置、简化部署、提升开发效率。为了更好地实践微服务架构,Dubbo 从 2.5.8 版本开始, 分别针对了上述的三个场景,提供了更完善的支持。本文不讨论传统的 XML 配置方式,而是侧重介绍注解这种方式。外部配置、自动装配两种自动装配会在另外的文章中专门介绍。

注解介绍

@EnableDubbo

@EnableDubbo 注解是 @EnableDubboConfig@DubboComponentScan两者组合的便捷表达方式。与注解驱动相关的是 @DubboComponentScan

代码语言:javascript
复制
package org.apache.dubbo.config.spring.context.annotation;

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

通过 @EnableDubbo 可以在指定的包名下(通过 scanBasePackages),或者指定的类中(通过 scanBasePackageClasses)扫描 Dubbo 的服务提供者(以 @Service 标注)以及 Dubbo 的服务消费者(以 Reference 标注)。

扫描到 Dubbo 的服务提供方和消费者之后,对其做相应的组装并初始化,并最终完成服务暴露或者引用的工作。

当然,如果不使用外部化配置(External Configuration)的话,也可以直接使用 @DubboComponentScan

@Service

@Service 用来配置 Dubbo 的服务提供方,比如:

代码语言:javascript
复制
@Service
public class AnnotatedGreetingService implements GreetingService {
    public String sayHello(String name) {
        return "hello, " + name;
    }
}

通过 @Service 上提供的属性,可以进一步的定制化 Dubbo 的服务提供方:

代码语言:javascript
复制
package org.apache.dubbo.config.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE}) // #1
@Inherited
public @interface Service {
    Class<?> interfaceClass() default void.class; // #2
    String interfaceName() default ""; // #3
    String version() default ""; // #4
    String group() default ""; // #5
    boolean export() default true; // #6
    boolean register() default true; // #7
    
    String application() default ""; // #8
    String module() default ""; // #9
    String provider() default ""; // #10
    String[] protocol() default {}; // #11
    String monitor() default ""; // #12
    String[] registry() default {}; // #13
}

其中比较重要的有:

  1. @Service 只能定义在一个类上,表示一个服务的具体实现
  2. interfaceClass:指定服务提供方实现的 interface 的类
  3. interfaceName:指定服务提供方实现的 interface 的类名
  4. version:指定服务的版本号
  5. group:指定服务的分组
  6. export:是否暴露服务
  7. registry:是否向注册中心注册服务
  8. application:应用配置
  9. module:模块配置
  10. provider:服务提供方配置
  11. protocol:协议配置
  12. monitor:监控中心配置
  13. registry:注册中心配置

另外,需要注意的是,application、module、provider、protocol、monitor、registry(从 8 到 13)需要提供的是对应的 spring bean 的名字,而这些 bean 的组装要么通过传统的 XML 配置方式完成,要么通过现代的 Java Config 来完成。在本文中,将会展示 Java Config 的使用方式。

@Reference

@Reference 用来配置 Dubbo 的服务消费方,比如:

代码语言:javascript
复制
@Component
public class GreetingServiceConsumer {
    @Reference
    private GreetingService greetingService;

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

通过 @Reference 上提供的属性,可以进一步的定制化 Dubbo 的服务消费方:

代码语言:javascript
复制
package org.apache.dubbo.config.annotation;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.ANNOTATION_TYPE}) // #1
public @interface Reference {
    Class<?> interfaceClass() default void.class; // #2
    String interfaceName() default ""; // #3
    String version() default ""; // #4
    String group() default ""; // #5
    String url() default ""; // #6
    
    String application() default ""; // #7
    String module() default ""; // #8
    String consumer() default ""; // #9
    String protocol() default ""; // #10
    String monitor() default ""; // #11
    String[] registry() default {}; // #12
}

其中比较重要的有:

  1. @Reference 可以定义在类中的一个字段上,也可以定义在一个方法上,甚至可以用来修饰另一个 annotation,表示一个服务的引用。通常 @Reference 定义在一个字段上
  2. interfaceClass:指定服务的 interface 的类
  3. interfaceName:指定服务的 interface 的类名
  4. version:指定服务的版本号
  5. group:指定服务的分组
  6. url:通过指定服务提供方的 URL 地址直接绕过注册中心发起调用
  7. application:应用配置
  8. module:模块配置
  9. consumer:服务消费方配置
  10. protocol:协议配置
  11. monitor:监控中心配置
  12. registry:注册中心配置

另外,需要注意的是,application、module、consumer、protocol、monitor、registry(从 7 到 12)需要提供的是对应的 spring bean 的名字,而这些 bean 的组装要么通过传统的 XML 配置方式完成,要么通过现代的 Java Config 来完成。在本文中,将会展示 Java Config 的使用方式。

示例实战

了解了 @EnableDubbo@Service@Reference 的作用,下面以一个实际的例子来展示如何使用 annotation 来开发 Dubbo 应用。以下的代码可以在 https://github.com/dubbo/dubbo-samples/tree/master/dubbo-samples-annotation 中找到。

1. 接口定义

定义一个简单的 GreetingService 接口,里面只有一个简单的方法 sayHello 向调用者问好。

代码语言:javascript
复制
public interface GreetingService {
    String sayHello(String name);
}

2. 服务端:服务实现

实现 GreetingService 接口,并通过 @Service 来标注其为 Dubbo 的一个服务。

代码语言:javascript
复制
@Service
public class AnnotatedGreetingService implements GreetingService {
    public String sayHello(String name) {
        return "hello, " + name;
    }
}

3. 服务端:组装服务提供方

通过 Spring 中 Java Config 的技术(@Configuration)和 annotation 扫描(@EnableDubbo)来发现、组装、并向外提供 Dubbo 的服务。

代码语言:javascript
复制
@Configuration
@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.impl")
static class ProviderConfiguration {
    @Bean // #1
    public ProviderConfig providerConfig() {
        ProviderConfig providerConfig = new ProviderConfig();
        providerConfig.setTimeout(1000);
        return providerConfig;
    }

    @Bean // #2
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-provider");
        return applicationConfig;
    }

    @Bean // #3
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setProtocol("zookeeper");
        registryConfig.setAddress("localhost");
        registryConfig.setPort(2181);
        return registryConfig;
    }

    @Bean // #4
    public ProtocolConfig protocolConfig() {
        ProtocolConfig protocolConfig = new ProtocolConfig();
        protocolConfig.setName("dubbo");
        protocolConfig.setPort(20880);
        return protocolConfig;
    }
}

说明:

  • 通过 @EnableDubbo 指定在 com.alibaba.dubbo.samples.impl 下扫描所有标注有 @Service 的类
  • 通过 @Configuration 将 ProviderConfiguration 中所有的 @Bean 通过 Java Config 的方式组装出来并注入给 Dubbo 服务,也就是标注有 @Service 的类。这其中就包括了:
    1. ProviderConfig:服务提供方配置
    2. ApplicationConfig:应用配置
    3. RegistryConfig:注册中心配置
    4. ProtocolConfig:协议配置

4. 服务端:启动服务

main 方法中通过启动一个 Spring Context 来对外提供 Dubbo 服务。

代码语言:javascript
复制
public class ProviderBootstrap {
    public static void main(String[] args) throws Exception {
        new EmbeddedZooKeeper(2181, false).start(); // #1
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ProviderConfiguration.class); // #2
        context.start(); // #3
        System.in.read(); // #4
    }
}

说明:

  1. 启动一个嵌入式的 zookeeper 在 2181 端口上提供注册中心的服务
  2. 初始化一个 AnnotationConfigApplicationContext 的示例,并将 ProviderConfiguration 传入以完成 Dubbo 服务的自动发现和装配
  3. 启动 Spring Context,开始提供对外的 Dubbo 服务
  4. 因为是服务端,需要通过阻塞主线程来防止进程退出

启动服务端的 main 方法,将会看到下面的输出,代表服务端启动成功,并在注册中心(ZookeeperRegistry)上注册了 GreetingService 这个服务:

代码语言:javascript
复制
[01/08/18 02:12:51:051 CST] main  INFO transport.AbstractServer:  [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /192.168.99.1:20880, dubbo version: 2.6.2, current host: 192.168.99.1

[01/08/18 02:12:51:051 CST] main  INFO zookeeper.ZookeeperRegistry:  [DUBBO] Register: dubbo://192.168.99.1:20880/com.alibaba.dubbo.samples.api.GreetingService?anyhost=true&application=dubbo-annotation-provider&default.timeout=1000&dubbo=2.6.2&generic=false&interface=com.alibaba.dubbo.samples.api

5. 客户端:引用服务

通过 @Reference 来标记 GreetingService 接口的成员变量 greetingService 是一个 Dubbo 服务的引用,也就是说,可以简单的通过该接口向远端的服务提供方发起调用,而客户端并没有实现 GreetingService 接口。

代码语言:javascript
复制
@Component("annotatedConsumer")
public class GreetingServiceConsumer {
    @Reference
    private GreetingService greetingService;

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

6. 客户端:组装服务消费者

3. 服务端:组装服务提供方 类似,通过 Spring 中 Java Config 的技术(@Configuration)和 annotation 扫描(@EnableDubbo)来发现、组装 Dubbo 服务的消费者。

代码语言:javascript
复制
@Configuration
@EnableDubbo(scanBasePackages = "com.alibaba.dubbo.samples.action")
@ComponentScan(value = {"com.alibaba.dubbo.samples.action"})
static class ConsumerConfiguration {
    @Bean // #1
    public ApplicationConfig applicationConfig() {
        ApplicationConfig applicationConfig = new ApplicationConfig();
        applicationConfig.setName("dubbo-annotation-consumer");
        return applicationConfig;
    }

    @Bean // #2
    public ConsumerConfig consumerConfig() {
        ConsumerConfig consumerConfig = new ConsumerConfig();
        consumerConfig.setTimeout(3000);
        return consumerConfig;
    }

    @Bean // #3
    public RegistryConfig registryConfig() {
        RegistryConfig registryConfig = new RegistryConfig();
        registryConfig.setProtocol("zookeeper");
        registryConfig.setAddress("localhost");
        registryConfig.setPort(2181);
        return registryConfig;
    }
}

说明:

  • 通过 @EnableDubbo 指定在 com.alibaba.dubbo.samples.impl 下扫描所有标注有 `@Reference 的类
  • 通过 @Configuration 将 ConsumerConfiguration 中所有的 @Bean 通过 Java Config 的方式组装出来并注入给 Dubbo 服务消费者,也就是标注有 `@Reference 的类。这其中就包括了:
    1. ApplicationConfig:应用配置
    2. ConsumerConfig:服务消费者配置
    3. RegistryConfig:注册中心配置,注意:这里的配置需要与服务提供方启动的 EmbeddedZooKeeper 的配置信息保持一致

7. 客户端:发起远程调用

main 方法中通过启动一个 Spring Context,从其中查找到组装好的 Dubbo 的服务消费者,并发起一次远程调用。

代码语言:javascript
复制
public class ConsumerBootstrap {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConsumerConfiguration.class); // #1
        context.start(); // #2
        GreetingServiceConsumer greetingServiceConsumer = context.getBean(GreetingServiceConsumer.class); // #3
        String hello = greetingServiceConsumer.doSayHello("annotation"); // #4
        System.out.println("result: " + hello); // #5
    }
}

说明:

  1. 初始化一个 AnnotationConfigApplicationContext 的示例,并将 ConsumerConfiguration 传入以完成 Dubbo 服务消费者的自动发现和装配
  2. 启动 Spring Context
  3. 从 Context 中查找出类型为 GreetingServiceConsumer 的 Bean
  4. 调用 doSayHello 方法,最终通过 Dubbo 的服务引用(由 @Reference 标注)发起一次远程调用
  5. 打印调用结果

启动客户端的 main 方法,将会看到下面的输出,其中返回结果为 result: hello, annotation:

代码语言:javascript
复制
[01/08/18 02:38:40:040 CST] main  INFO config.AbstractConfig:  [DUBBO] Refer dubbo service com.alibaba.dubbo.samples.api.GreetingService from url zookeeper://localhost:2181/com.alibaba.dubbo.registry.RegistryService?anyhost=true&application=dubbo-annotation-consumer&check=false&default.timeout=3000&dubbo=2.6.2&generic=false&interface=com.alibaba.dubbo.samples.api.GreetingService&methods=sayHello&pid=33001&register.ip=192.168.99.1&remote.timestamp=1533105502086&side=consumer&timestamp=1533105519216, dubbo version: 2.6.2, current host: 192.168.99.1
[01/08/18 02:38:40:040 CST] main  INFO annotation.ReferenceBeanBuilder: <dubbo:reference object="com.alibaba.dubbo.common.bytecode.proxy0@673be18f" singleton="true" interface="com.alibaba.dubbo.samples.api.GreetingService" uniqueServiceName="com.alibaba.dubbo.samples.api.GreetingService" generic="false" id="com.alibaba.dubbo.samples.api.GreetingService" /> has been built.
result: hello, annotation

总结

通过本文的学习,读者可以掌握 Dubbo 专属的 annotation @EnableDubbo@Service@Reference 的基本概念,并通过一个简单 Dubbo 应用的实战开发掌握其基本的用法。

Spring 除了传统的 XML 配置之外,还提供了注解驱动、外部化配置、以及自动装配等更现代的配置方式。本文专注在介绍通过注解方式来开发 Dubbo 应用,可以看到,与 XML 配置相比,注解方式编程更加简洁明快。在今后的博文中,会进一步的介绍在 Dubbo 中使用外部化配置、以及自动装配的方法。

下面介绍@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。定义如下:

代码语言:javascript
复制
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:

代码语言:javascript
复制
package com.alibaba.dubbo.demo;

public interface DemoService {

    String sayHello(String name);

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

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

代码语言:javascript
复制
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:

代码语言:javascript
复制
<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 的形式:

代码语言:javascript
复制
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;
    }
}

服务提供方引导类

代码语言:javascript
复制
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启动并执行后,控制输出与预期一致:

代码语言:javascript
复制
Hello , World

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

服务消费方(@Reference
服务 DemoService
代码语言:javascript
复制
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

代码语言:javascript
复制
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

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

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

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

完整的引导类实现:

代码语言:javascript
复制
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输出:

代码语言:javascript
复制
Hello , World

Spring AOP 支持

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

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

代码语言:javascript
复制
@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注解:

代码语言:javascript
复制
@Service
@Transactional
public class AnnotationDemoService implements DemoService {
    // 省略实现,保持不变
}

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

代码语言:javascript
复制
get transaction ...
commit transaction ...
Hello , World

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

注意事项

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

代码语言:javascript
复制
WARN : [DUBBO] No Spring Bean annotating Dubbo's @Service was found in SpringBeanFactory, 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/apache/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 不兼容情况:

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

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

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

问题版本:2.5.72.5.8

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

整体愿景

随着微服务架构的广泛地推广和实施。在 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 应用,两者之间可以实现无缝迁移(无需任何调整)。

注解驱动(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 配置文件配合使用,如下所示:

代码语言:javascript
复制
  <?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

代码语言:javascript
复制
  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> 扫描后:

代码语言:javascript
复制
  <dubbo:annotation package="com.alibaba.dubbo.examples.annotation.action"/>

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

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

AnnotationAction.java

代码语言:javascript
复制
  @Component("annotationAction")
  public class AnnotationAction extends BaseAction {
  
      public String doSayHello(String name) {
          return getAnnotationService().sayHello(name);
      }
  
  }

BaseAction.java

代码语言:javascript
复制
  public abstract class BaseAction {
  
      @Reference
      private AnnotationService annotationService;
  
      protected AnnotationService getAnnotationService() {
          return annotationService;
      }
  }

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

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

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 在 Dubbo 中使用注解
    • 注解介绍
      • @EnableDubbo
      • @Service
      • @Reference
    • 示例实战
      • 1. 接口定义
      • 2. 服务端:服务实现
      • 3. 服务端:组装服务提供方
      • 4. 服务端:启动服务
      • 5. 客户端:引用服务
      • 6. 客户端:组装服务消费者
      • 7. 客户端:发起远程调用
    • 总结
      • 设计原则
        • 使用方法
          • Spring AOP 支持
          • 注意事项
        • 整体愿景
          • 注解驱动(Annotation-Driven)
            • @DubboComponentScan
        相关产品与服务
        微服务引擎 TSE
        微服务引擎(Tencent Cloud Service Engine)提供开箱即用的云上全场景微服务解决方案。支持开源增强的云原生注册配置中心(Zookeeper、Nacos 和 Apollo),北极星网格(腾讯自研并开源的 PolarisMesh)、云原生 API 网关(Kong)以及微服务应用托管的弹性微服务平台。微服务引擎完全兼容开源版本的使用方式,在功能、可用性和可运维性等多个方面进行增强。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档