Dubbo 注解驱动(Annotation-Driven)
?
注解驅(qū)動(dòng)(Annotation-Driven)
@DubboComponentScan
起始版本:?2.5.7
<dubbo:annotation>歷史遺留問(wèn)題
1. 注解支持不充分
在 Dubbo?2.5.7之前的版本 ,Dubbo 提供了兩個(gè)核心注解?@Service?以及?@Reference,分別用于Dubbo 服務(wù)提供和 Dubbo 服務(wù)引用。
其中,@Service?作為 XML 元素?<dubbo:service>的替代注解,與 Spring Framework@org.springframework.stereotype.Service?類似,用于服務(wù)提供方 Dubbo 服務(wù)暴露。與之相對(duì)應(yīng)的@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.xsdhttp://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
同時(shí),使用?<dubbo:annotation>?方式掃描后的Dubbo?@Service?,在 Spring 代理方面存在問(wèn)題,如 GitHub 上的 issue?https://github.com/alibaba/dubbo/issues/794:
關(guān)于dubbo @Service注解生成ServiceBean時(shí), interface獲取成spring 的代理對(duì)象的bug
在項(xiàng)目里, 我使用了
@Service @Transactional @com.alibaba.dubbo.config.annotation.Service public class SUserJpushServiceImp的形式, 來(lái)暴露服務(wù)。但是在發(fā)布服務(wù)的時(shí)候, interface class 是通過(guò)serviceConfig.setInterface(bean.getClass().getInterfaces()[0]);?的形式獲取, 剛好, 我的service都使用了@Transactional注解, 對(duì)象被代理了。所以獲取到的interface是Spring的代理接口...
不少熱心的小伙伴不僅發(fā)現(xiàn)這個(gè)歷史遺留問(wèn)題,而且提出了一些修復(fù)方案。同時(shí),為了更好地適配 Spring 生命周期以及將 Dubbo 完全向注解驅(qū)動(dòng)編程模型過(guò)渡,因此,引入了全新 Dubbo 組件掃描注解 -?@DubboComponentScan。
注:?<dubbo:annotation>?Spring AOP 問(wèn)題將在?2.5.9?中修復(fù):https://github.com/alibaba/dubbo/issues/1125
3. @Reference 不支持字段繼承性
假設(shè)有一個(gè) Spring Bean?AnnotationAction?直接通過(guò)字段annotationService?標(biāo)記?@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 {@Referenceprivate AnnotationService annotationService;public String doSayHello(String name) {return annotationService.sayHello(name);}}當(dāng)AnnotationAction?被 XML 元素?<dubbo:annotation>?掃描后:
<dubbo:annotation package="com.alibaba.dubbo.examples.annotation.action"/>字段?annotationService?能夠引用到?AnnotationService,執(zhí)行?doSayHello?方法能夠正常返回。
如果將字段annotationService?抽取到AnnotationAction?的父類BaseAction?后,AnnotationService?無(wú)法再被引用,改造如下所示:
AnnotationAction.java
@Component("annotationAction") public class AnnotationAction extends BaseAction {public String doSayHello(String name) {return getAnnotationService().sayHello(name);}}BaseAction.java
public abstract class BaseAction {@Referenceprivate AnnotationService annotationService;protected AnnotationService getAnnotationService() {return annotationService;} }改造后,再次執(zhí)行?doSayHello?方法,NullPointerException?將會(huì)被拋出。說(shuō)明<dubbo:annotation>?并不支持@Reference?字段繼承性。
了解了歷史問(wèn)題,集合整體愿景,下面介紹@DubboComponentScan?的設(shè)計(jì)原則。
設(shè)計(jì)原則
Spring Framework 3.1 引入了新 Annotation -?@ComponentScan?, 完全替代了 XML 元素<context:component-scan>?。同樣,?@DubboComponentScan?作為 Dubbo?2.5.7?新增的 Annotation,也是XML 元素?<dubbo:annotation>?的替代方案。
在命名上(類名以及屬性方法),為了簡(jiǎn)化使用和關(guān)聯(lián)記憶,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)指定的包名
在職責(zé)上,@DubboComponentScan?相對(duì)于 Spring Boot?@ServletComponentScan?更為繁重,原因在于處理 Dubbo?@Service?類暴露 Dubbo 服務(wù)外,還有幫助 Spring Bean?@Reference字段或者方法注入 Dubbo 服務(wù)代理。
在場(chǎng)景上,Spring Framework?@ComponentScan?組件掃描邏輯更為復(fù)雜。而在?@DubboComponentScan?只需關(guān)注?@Service?和?@Reference?處理。
在功能上,?@DubboComponentScan?不但需要提供完整 Spring AOP 支持的能力,而且還得具備@Reference?字段可繼承性的能力。
了解基本設(shè)計(jì)原則后,下面通過(guò)完整的示例,簡(jiǎn)介@DubboComponentScan?使用方法以及注意事項(xiàng)。
使用方法
后續(xù)通過(guò)服務(wù)提供方(@Serivce)以及服務(wù)消費(fèi)方(@Reference)兩部分來(lái)介紹@DubboComponentScan使用方法。
假設(shè),服務(wù)提供方和服務(wù)消費(fèi)分均依賴服務(wù)接口DemoService:
package com.alibaba.dubbo.demo;public interface DemoService {String sayHello(String name);}服務(wù)提供方(@Serivce)
實(shí)現(xiàn)?DemoService
服務(wù)提供方實(shí)現(xiàn)DemoService?-?AnnotationDemoService?,同時(shí)標(biāo)注 Dubbo?@Service?:
package com.alibaba.dubbo.demo.provider;import com.alibaba.dubbo.config.annotation.Service; import com.alibaba.dubbo.demo.DemoService;/*** Annotation {@link DemoService} 實(shí)現(xiàn)** @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>*/ @Service public class AnnotationDemoService implements DemoService {@Overridepublic String sayHello(String name) {return "Hello , " + name;}}服務(wù)提供方 Annotation 配置
將?AnnotationDemoService?暴露成Dubbo 服務(wù),需要依賴 Spring Bean:AplicationConfig、ProtocolConfig?以及?RegistryConfig?。這三個(gè) Spring Bean 過(guò)去可通過(guò) 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.xsdhttp://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"><!-- 當(dāng)前應(yīng)用信息配置 --><dubbo:application name="dubbo-annotation-provider"/><!-- 連接注冊(cè)中心配置 --><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;/*** 服務(wù)提供方配置** @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>*/ @Configuration @DubboComponentScan("com.alibaba.dubbo.demo.provider") // 掃描 Dubbo 組件 public class ProviderConfiguration {/*** 當(dāng)前應(yīng)用配置*/@Bean("dubbo-annotation-provider")public ApplicationConfig applicationConfig() {ApplicationConfig applicationConfig = new ApplicationConfig();applicationConfig.setName("dubbo-annotation-provider");return applicationConfig;}/*** 當(dāng)前連接注冊(cè)中心配置*/@Bean("my-registry")public RegistryConfig registryConfig() {RegistryConfig registryConfig = new RegistryConfig();registryConfig.setAddress("N/A");return registryConfig;}/*** 當(dāng)前連接注冊(cè)中心配置*/@Bean("dubbo")public ProtocolConfig protocolConfig() {ProtocolConfig protocolConfig = new ProtocolConfig();protocolConfig.setName("dubbo");protocolConfig.setPort(12345);return protocolConfig;} }服務(wù)提供方引導(dǎo)類
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;/*** 服務(wù)提供方引導(dǎo)類** @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>*/ public class ProviderBootstrap {public static void main(String[] args) {// 創(chuàng)建 Annotation 配置上下文AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();// 注冊(cè)配置 Beancontext.register(ProviderConfiguration.class);// 啟動(dòng)上下文context.refresh();// 獲取 DemoService BeanDemoService demoService = context.getBean(DemoService.class);// 執(zhí)行 sayHello 方法String message = demoService.sayHello("World");// 控制臺(tái)輸出信息System.out.println(message);}}ProviderBootstrap?啟動(dòng)并執(zhí)行后,控制輸出與預(yù)期一致:
Hello , World以上直接結(jié)果說(shuō)明?@DubboComponentScan("com.alibaba.dubbo.demo.provider")?掃描后,標(biāo)注 Dubbo@Service?的?AnnotationDemoService?被注冊(cè)成 Spring Bean,可從 Spring ApplicationContext 自由獲取。
服務(wù)消費(fèi)方(@Reference)
服務(wù)?DemoService
package com.alibaba.dubbo.demo.consumer;import com.alibaba.dubbo.config.annotation.Reference; import com.alibaba.dubbo.demo.DemoService;/*** Annotation 驅(qū)動(dòng) {@link DemoService} 消費(fèi)方** @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);} }服務(wù)消費(fèi)方 Annotation 配置
與服務(wù)提供方配置類似,服務(wù)消費(fèi)方也許 Dubbo 相關(guān)配置 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;/*** 服務(wù)消費(fèi)方配置** @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>*/ @Configuration @DubboComponentScan public class ConsumerConfiguration {/*** 當(dāng)前應(yīng)用配置*/@Beanpublic ApplicationConfig applicationConfig() {ApplicationConfig applicationConfig = new ApplicationConfig();applicationConfig.setName("dubbo-annotation-consumer");return applicationConfig;}/*** 當(dāng)前連接注冊(cè)中心配置*/@Beanpublic RegistryConfig registryConfig() {RegistryConfig registryConfig = new RegistryConfig();registryConfig.setAddress("N/A");return registryConfig;}/*** 注冊(cè) AnnotationDemoServiceConsumer,@DubboComponentScan 將處理其中 @Reference 字段。* 如果 AnnotationDemoServiceConsumer 非 Spring Bean 的話,* 即使 @DubboComponentScan 指定 package 也不會(huì)進(jìn)行處理,與 Spring @Autowired 同理*/@Beanpublic AnnotationDemoServiceConsumer annotationDemoServiceConsumer() {return new AnnotationDemoServiceConsumer();}}服務(wù)消費(fèi)方引導(dǎo)類
服務(wù)消費(fèi)方需要先引導(dǎo)服務(wù)提供方,下面的實(shí)例將會(huì)啟動(dòng)兩個(gè) Spring 應(yīng)用上下文,首先引導(dǎo)服務(wù)提供方 Spring 應(yīng)用上下文,同時(shí),需要復(fù)用前面Annotation 配置?ProviderConfiguration:
/*** 啟動(dòng)服務(wù)提供方上下文*/private static void startProviderContext() {// 創(chuàng)建 Annotation 配置上下文AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();// 注冊(cè)配置 BeanproviderContext.register(ProviderConfiguration.class);// 啟動(dòng)服務(wù)提供方上下文providerContext.refresh();}然后引導(dǎo)服務(wù)消費(fèi)方Spring 應(yīng)用上下文:
/*** 啟動(dòng)并且返回服務(wù)消費(fèi)方上下文** @return AnnotationConfigApplicationContext*/private static ApplicationContext startConsumerContext() {// 創(chuàng)建服務(wù)消費(fèi)方 Annotation 配置上下文AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();// 注冊(cè)服務(wù)消費(fèi)方配置 BeanconsumerContext.register(ConsumerConfiguration.class);// 啟動(dòng)服務(wù)消費(fèi)方上下文consumerContext.refresh();// 返回服務(wù)消費(fèi)方 Annotation 配置上下文return consumerContext;}完整的引導(dǎo)類實(shí)現(xiàn):
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;/*** 服務(wù)消費(fèi)端引導(dǎo)類** @author <a href="mailto:mercyblitz@gmail.com">Mercy</a>*/ public class ConsumerBootstrap {public static void main(String[] args) {// 啟動(dòng)服務(wù)提供方上下文startProviderContext();// 啟動(dòng)并且返回服務(wù)消費(fèi)方上下文ApplicationContext consumerContext = startConsumerContext();// 獲取 AnnotationDemoServiceConsumer BeanAnnotationDemoServiceConsumer consumer = consumerContext.getBean(AnnotationDemoServiceConsumer.class);// 執(zhí)行 doSayHello 方法String message = consumer.doSayHello("World");// 輸出執(zhí)行結(jié)果System.out.println(message);}/*** 啟動(dòng)并且返回服務(wù)消費(fèi)方上下文** @return AnnotationConfigApplicationContext*/private static ApplicationContext startConsumerContext() {// 創(chuàng)建服務(wù)消費(fèi)方 Annotation 配置上下文AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();// 注冊(cè)服務(wù)消費(fèi)方配置 BeanconsumerContext.register(ConsumerConfiguration.class);// 啟動(dòng)服務(wù)消費(fèi)方上下文consumerContext.refresh();// 返回服務(wù)消費(fèi)方 Annotation 配置上下文return consumerContext;}/*** 啟動(dòng)服務(wù)提供方上下文*/private static void startProviderContext() {// 創(chuàng)建 Annotation 配置上下文AnnotationConfigApplicationContext providerContext = new AnnotationConfigApplicationContext();// 注冊(cè)配置 BeanproviderContext.register(ProviderConfiguration.class);// 啟動(dòng)服務(wù)提供方上下文providerContext.refresh();}}運(yùn)行ConsumerBootstrap結(jié)果,仍然符合期望,AnnotationDemoServiceConsumer?輸出:
Hello , WorldSpring AOP 支持
前面提到?<dubbo:annotation>?注冊(cè) Dubbo?@Service?組件后,在 Spring AOP 支持方面存在問(wèn)題。事務(wù)作為 Spring AOP 的功能擴(kuò)展,自然也會(huì)在?<dubbo:annotation>中不支持。
@DubboComponentScan?針對(duì)以上問(wèn)題,實(shí)現(xiàn)了對(duì) Spring AOP 是完全兼容。將上述服務(wù)提供方 Annotation 配置做出一定的調(diào)整,標(biāo)注@EnableTransactionManagement?以及自定義實(shí)現(xiàn)PlatformTransactionManager:
@Configuration @DubboComponentScan("com.alibaba.dubbo.demo.provider") // 掃描 Dubbo 組件 @EnableTransactionManagement // 激活事務(wù)管理 public class ProviderConfiguration {// 省略其他配置 Bean 定義/*** 自定義事務(wù)管理器*/@Bean@Primarypublic PlatformTransactionManager transactionManager() {return new PlatformTransactionManager() {@Overridepublic TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {System.out.println("get transaction ...");return new SimpleTransactionStatus();}@Overridepublic void commit(TransactionStatus status) throws TransactionException {System.out.println("commit transaction ...");}@Overridepublic void rollback(TransactionStatus status) throws TransactionException {System.out.println("rollback transaction ...");}};} }同時(shí)調(diào)整?AnnotationDemoService?- 增加@Transactional?注解:
@Service @Transactional public class AnnotationDemoService implements DemoService {// 省略實(shí)現(xiàn),保持不變 }再次運(yùn)行ConsumerBootstrap?, 觀察控制臺(tái)輸出內(nèi)容:
get transaction ... commit transaction ... Hello , World輸入內(nèi)容中多處了兩行,說(shuō)明自定義?PlatformTransactionManagergetTransaction(TransactionDefinition)?以及?commit(TransactionStatus)?方法被執(zhí)行,進(jìn)而說(shuō)明AnnotationDemoService?的sayHello(String)?方法執(zhí)行時(shí),事務(wù)也伴隨執(zhí)行。
注意事項(xiàng)
ConsumerConfiguration?上的?@DubboComponentScan?并沒(méi)有指定?basePackages?掃描,這種情況會(huì)將ConsumerConfiguration?當(dāng)做?basePackageClasses?,即掃描ConsumerConfiguration?所屬的 packagecom.alibaba.dubbo.demo.config?以及子 package。由于當(dāng)前示例中,不存在標(biāo)注 Dubbo?@Service的類,因此在運(yùn)行時(shí)日志(如果開(kāi)啟的話)會(huì)輸出警告信息:
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以上信息大可不必?fù)?dān)憂,因?yàn)?@DubboComponentScan?除了掃描 Dubbo?@Service?組件以外,還將處理@Reference字段注入。然而讀者特別關(guān)注@Reference字段注入的規(guī)則。
以上實(shí)現(xiàn)為例,AnnotationDemoServiceConsumer?必須申明為 Spring?@Bean?或者?@Component(或者其派生注解),否則?@DubboComponentScan?不會(huì)主動(dòng)將標(biāo)注?@Reference字段所在的聲明類提成為 Spring Bean,換句話說(shuō),如果?@Reference字段所在的聲明類不是 Spring Bean 的話,?@DubboComponentScan?不會(huì)處理@Reference注入,其原理與 Spring?@Autowired?一致。
以上使用不當(dāng)可能會(huì)導(dǎo)致相關(guān)問(wèn)題,如 GitHub 上曾有小伙伴提問(wèn):https://github.com/alibaba/dubbo/issues/825
li362692680?提問(wèn):
@DubboComponentScan注解在消費(fèi)端掃描包時(shí)掃描的是 @Service注解??不是@Reference注解?? 啟動(dòng)時(shí)報(bào) DubboComponentScanRegistrar-85]-[main]-[INFO] 0 annotated @Service Components { [] }
筆者(mercyblitz)回復(fù):
@Reference?類似于?@Autowired?一樣,首先其申明的類必須被 Spring 上下文當(dāng)做一個(gè)Bean,因此,Dubbo 并沒(méi)有直接將?@Reference?字段所在的類提升成 Bean。
綜上所述,這并不是一個(gè)問(wèn)題,而是用法不當(dāng)!
已知問(wèn)題
最新發(fā)布的 Dubbo?2.5.8?中,@DubboComponentScan?在以下特殊場(chǎng)景下存在 Spring?@Service?不兼容情況:
假設(shè)有兩個(gè)服務(wù)實(shí)現(xiàn)類?A?和?B,同時(shí)存放在com.acme?包下:
- A?標(biāo)注 Dubbo?@Service
- B?標(biāo)注 Dubbo?@Service?和 Spring?@Service
當(dāng) Spring?@ComponentScan?先掃描com.acme?包時(shí),B?被當(dāng)做 Spring Bean 的候選類。隨后,@DubboComponentScan?也掃描相同的包。當(dāng)應(yīng)用啟動(dòng)時(shí),A?和?B?雖然都是 Spring Bean,可僅A?能夠暴露 Dubbo 服務(wù),B?則丟失。
問(wèn)題版本:2.5.7、2.5.8
問(wèn)題詳情:https://github.com/alibaba/dubbo/issues/1120
修復(fù)版本:2.5.9(下個(gè)版本)
原文:http://dubbo.apache.org/zh-cn/blog/dubbo-annotation-driven.html
總結(jié)
以上是生活随笔為你收集整理的Dubbo 注解驱动(Annotation-Driven)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何使用Fescar保证Dubbo微服务
- 下一篇: Dubbo 融合 Nacos 成为注册中