隨着微服務架構的普遍地推廣和實施。在 Java 生態系統中,以 Spring Boot 和 Spring Cloud 爲表明的微服務框架,引入了全新的編程模型,包括註解驅動(Annotation-Driven)、外部化配置(External Configuration)以及自動裝配(Auto-Configure)等。新的編程模型無需 XML 配置、簡化部署、提高開發效率。java
爲了更好地實踐微服務架構,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 應用,二者之間能夠實現無縫遷移(無需任何調整)。git
@DubboComponentScan
2.5.7
<dubbo:annotation>
歷史遺留問題在 Dubbo 2.5.7
以前的版本 ,Dubbo 提供了兩個核心註解 @Service
以及 @Reference
,分別用於Dubbo 服務提供和 Dubbo 服務引用。github
其中,@Service
做爲 XML 元素 <dubbo:service>
的替代註解,與 Spring Framework @org.springframework.stereotype.Service
相似,用於服務提供方 Dubbo 服務暴露。與之相對應的@Reference
,則是替代<dubbo:reference
元素,相似於 Spring 中的 @Autowired
。spring
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/du...:bootstrap
關於dubbo @Service註解生成ServiceBean時, interface獲取成spring 的代理對象的bugsegmentfault
在項目裏, 我使用了api
@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/du...
假設有一個 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; } }
將 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
輸入內容中多處了兩行,說明自定義 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/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
不兼容狀況:
假設有兩個服務實現類
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/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 以及 微服務相關內容,請關注小馬哥公衆號: