资讯专栏INFORMATION COLUMN

Dubbo 新编程模型之注解驱动

chavesgu / 2373人阅读

摘要:在生态系统中,以和为代表的微服务框架,引入了全新的编程模型,包括注解驱动外部化配置以及自动装配等。新的编程模型无需配置简化部署提升开发效率。同时,新的编程模型也是即将发布的的基础设施。

原文地址 整体愿景

随着微服务架构的广泛地推广和实施。在 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 历史遗留问题
1. 注解支持不充分

在 Dubbo 2.5.7之前的版本 ,Dubbo 提供了两个核心注解 @Service 以及 @Reference,分别用于Dubbo 服务提供和 Dubbo 服务引用。

其中,@Service 作为 XML 元素 的替代注解,与 Spring Framework @org.springframework.stereotype.Service 类似,用于服务提供方 Dubbo 服务暴露。与之相对应的@Reference,则是替代 元素,类似于 Spring 中的 @Autowired

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




    
    
    

2. @Service Bean 不支持 Spring AOP

同时,使用 方式扫描后的Dubbo @Service ,在 Spring 代理方面存在问题,如 GitHub 上的 issue https://github.com/alibaba/du...:

关于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

注:    Spring AOP 问题将在 2.5.9 中修复:https://github.com/alibaba/du...
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 元素 扫描后:

字段 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 将会被抛出。说明 并不支持@Reference 字段继承性。

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

设计原则

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

在命名上(类名以及属性方法),为了简化使用和关联记忆,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.
     * 

* 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 Mercy
 */
@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:



    
    

    
    

    

以上装配方式不予推荐,推荐使用 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 Mercy
 */
@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 Mercy
 */
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 @ServiceAnnotationDemoService 被注册成 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 Mercy
 */
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 Mercy
 */
@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 Mercy
 */
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 @Service 组件后,在 Spring AOP 支持方面存在问题。事务作为 Spring AOP 的功能扩展,自然也会在 中不支持。

@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) 方法被执行,进而说明 AnnotationDemoServicesayHello(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/du...

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/alibaba/du...

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

关于作者

小马哥,十余年Java EE 从业经验,架构师、微服务布道师、Dubbo 维护者。目前主要负责阿里巴巴集团微服务技术实施、架构衍进、基础设施构建等。重点关注云计算、微服务以及软件架构等领域。通过SUN Java(SCJP、SCWCD、SCBCD)以及Oracle OCA 等的认证。

github:https://github.com/mercyblitz

sf.gg : https://segmentfault.com/u/me...

微信/微博:mercyblitz

下篇预告 《Dubbo 新编程模型之外部化配置(External Configuration)》

更多 Dubbo 以及 微服务相关内容,请关注小马哥公众号:

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/68075.html

相关文章

  • Dubbo 新编模型外部化配置

    摘要:同时,所标注的需要被应用上下文注册配置引导类创建配置上下文注册当前配置获取和获取获取运行结果运行后控制台输出输出的内容与绑定的内容一致,符合期望。 Dubbo 外部化配置(Externalized Configuration) 原文地址 外部化配置(Externalized Configuration) 在Dubbo 注解驱动例子中,无论是服务提供方,还是服务消费方,均需要转配相关配置...

    baihe 评论0 收藏0
  • 官方 Dubbo Spring Boot Starter 1.0.0 公测版

    摘要:公告今天小马哥非常高兴地向各位小伙伴宣布,官方公测版已开发完毕,即将发布至公有仓库,目前正在内部测试中。为了收集更多的用户反馈,小马哥诚邀大家一同参与使用测试以及共同维护,项目工程地址如果您喜爱并想了解工程的动态,不妨点击按钮加以关注。 公告 今天小马哥非常高兴地向各位小伙伴宣布,官方 Dubbo Spring Boot Starter 1.0.0 公测版已开发完毕,即将发布至 Mav...

    MrZONT 评论0 收藏0
  • Dubbo Cloud Native 实践与思考

    摘要:可简单地认为它是的扩展,负载均衡自然成为不可或缺的特性。类似的特性在项目也有体现,它是另一种高性能代理的方案,提供服务发现健康和负载均衡。 Dubbo Cloud Native 实践与思考 分享简介 Cloud Native 应用架构随着云技术的发展受到业界特别重视和关注,尤其是 CNCF(Cloud Native Computing Foundation)项目蓬勃发展之际。Dubbo...

    邱勇 评论0 收藏0
  • IMI 基于 Swoole 开发的协 PHP 开发框架 常驻内存、协异步非阻塞

    摘要:介绍是基于开发的协程开发框架,拥有常驻内存协程异步非阻塞等优点。宇润我在年开发并发布了第一个框架,一直维护使用至今,非常稳定,并且有文档。于是我走上了开发的不归路 showImg(https://segmentfault.com/img/bVbcxQH?w=340&h=160); 介绍 IMI 是基于 Swoole 开发的协程 PHP 开发框架,拥有常驻内存、协程异步非阻塞IO等优点。...

    airborne007 评论0 收藏0
  • Dubbo Spring Cloud 重塑微服务治理

    摘要:在服务治理方面,相较于而言,并不成熟。遗憾的是,往往被部分开发者片面地视作服务治理的框架,而非微服务基础设施。因此,建议开发人员将或者迁移为服务。因此,下一步需要将其配置服务远程。当服务提供方启动后,下一步实现一个服务消费方。 原文链接:Dubbo Spring Cloud 重塑微服务治理,来自于微信公众号:次灵均阁 摘要 在 Java 微服务生态中,Spring Cloud1 成为...

    wh469012917 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<