通过 @EnableDubbo 可以在指定的包名下(通过 scanBasePackages),或者指定的类中(通过 scanBasePackageClasses)扫描 Dubbo 的服务提供者(以 @Service 标注)以及 Dubbo 的服务消费者(以 Reference 标注)。
扫描到 Dubbo 的服务提供方和消费者之后,对其做相应的组装并初始化,并最终完成服务暴露或者引用的工作。
当然,如果不使用外部化配置(External Configuration)的话,也可以直接使用 @DubboComponentScan。
随着微服务架构的广泛地推广和实施。在 Java 生态系统中,以 Spring Boot 和 Spring Cloud 为代表的微服务框架,引入了全新的编程模型,包括:
新的编程模型无需 XML 配置、简化部署、提升开发效率。为了更好地实践微服务架构,Dubbo 从 2.5.8
版本开始, 分别针对了上述的三个场景,提供了更完善的支持。本文不讨论传统的 XML 配置方式,而是侧重介绍注解这种方式。外部配置、自动装配两种自动装配会在另外的文章中专门介绍。
@EnableDubbo
注解是 @EnableDubboConfig
和 @DubboComponentScan
两者组合的便捷表达方式。与注解驱动相关的是 @DubboComponentScan
。
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
用来配置 Dubbo 的服务提供方,比如:
@Service
public class AnnotatedGreetingService implements GreetingService {
public String sayHello(String name) {
return "hello, " + name;
}
}
通过 @Service
上提供的属性,可以进一步的定制化 Dubbo 的服务提供方:
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
}
其中比较重要的有:
另外,需要注意的是,application、module、provider、protocol、monitor、registry(从 8 到 13)需要提供的是对应的 spring bean 的名字,而这些 bean 的组装要么通过传统的 XML 配置方式完成,要么通过现代的 Java Config 来完成。在本文中,将会展示 Java Config 的使用方式。
@Reference
用来配置 Dubbo 的服务消费方,比如:
@Component
public class GreetingServiceConsumer {
@Reference
private GreetingService greetingService;
public String doSayHello(String name) {
return greetingService.sayHello(name);
}
}
通过 @Reference
上提供的属性,可以进一步的定制化 Dubbo 的服务消费方:
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
}
其中比较重要的有:
另外,需要注意的是,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 中找到。
定义一个简单的 GreetingService
接口,里面只有一个简单的方法 sayHello
向调用者问好。
public interface GreetingService {
String sayHello(String name);
}
实现 GreetingService
接口,并通过 @Service
来标注其为 Dubbo 的一个服务。
@Service
public class AnnotatedGreetingService implements GreetingService {
public String sayHello(String name) {
return "hello, " + name;
}
}
通过 Spring 中 Java Config 的技术(@Configuration
)和 annotation 扫描(@EnableDubbo
)来发现、组装、并向外提供 Dubbo 的服务。
@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
的类。这其中就包括了:
在 main
方法中通过启动一个 Spring Context 来对外提供 Dubbo 服务。
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
}
}
说明:
AnnotationConfigApplicationContext
的示例,并将 ProviderConfiguration
传入以完成 Dubbo 服务的自动发现和装配启动服务端的 main
方法,将会看到下面的输出,代表服务端启动成功,并在注册中心(ZookeeperRegistry)上注册了 GreetingService
这个服务:
[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
通过 @Reference
来标记 GreetingService
接口的成员变量 greetingService 是一个 Dubbo 服务的引用,也就是说,可以简单的通过该接口向远端的服务提供方发起调用,而客户端并没有实现 GreetingService
接口。
@Component("annotatedConsumer")
public class GreetingServiceConsumer {
@Reference
private GreetingService greetingService;
public String doSayHello(String name) {
return greetingService.sayHello(name);
}
}
与 3. 服务端:组装服务提供方 类似,通过 Spring 中 Java Config 的技术(@Configuration
)和 annotation 扫描(@EnableDubbo
)来发现、组装 Dubbo 服务的消费者。
@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 的类。这其中就包括了:在 main
方法中通过启动一个 Spring Context,从其中查找到组装好的 Dubbo 的服务消费者,并发起一次远程调用。
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
}
}
说明:
AnnotationConfigApplicationContext
的示例,并将 ConsumerConfiguration
传入以完成 Dubbo 服务消费者的自动发现和装配GreetingServiceConsumer
的 BeandoSayHello
方法,最终通过 Dubbo 的服务引用(由 @Reference
标注)发起一次远程调用启动客户端的 main
方法,将会看到下面的输出,其中返回结果为 result: hello, annotation:
[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®ister.ip=192.168.99.1&remote.timestamp=1533105502086&side=consumer×tamp=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
。定义如下:
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;
}
}
将 AnnotationDemoService
暴露成Dubbo 服务,需要依赖 Spring Bean:AplicationConfig
、ProtocolConfig
以及 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);
}
}
与服务提供方配置类似,服务消费方也许 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
前面提到 <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
输入内容中多处了两行,说明自定义PlatformTransactionManagergetTransaction(TransactionDefinition)
以及commit(TransactionStatus)
方法被执行,进而说明AnnotationDemoService
的sayHello(String)
方法执行时,事务也伴随执行。
ConsumerConfiguration
上的@DubboComponentScan
并没有指定basePackages
扫描,这种情况会将ConsumerConfiguration
当做basePackageClasses
,即扫描ConsumerConfiguration
所属的 packagecom.alibaba.dubbo.demo.config
以及子 package。由于当前示例中,不存在标注 Dubbo@Service
的类,因此在运行时日志(如果开启的话)会输出警告信息:
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
不兼容情况:
假设有两个服务实现类
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.7
、2.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 应用,两者之间可以实现无缝迁移(无需任何调整)。
@DubboComponentScan
2.5.7
<dubbo:annotation>
历史遗留问题在 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>
@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
假设有一个 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
的设计原则。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文系转载,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。