本文主要介紹了在不同的配置模式下,dubbo與spring整合的原理,即:xml配置、注解配置、自動(dòng)化配置 三種模式下的配置生效原理。
XML啟動(dòng)
Schema擴(kuò)展機(jī)制
Spring提供了 Schema 擴(kuò)展機(jī)制,用戶可以自定義 Schema 文件,并自定義 Schema 解析器,然后集成到SpringIOC容器中。
創(chuàng)建自定義擴(kuò)展,主要有以下步驟:
- 創(chuàng)建 Schema 文件,描述自定義的合法構(gòu)建模塊,也就是xsd文件,主要用于定義數(shù)據(jù)約束;
- 自定義個(gè)處理器類,并實(shí)現(xiàn)NamespaceHandler接口,在里面注冊(cè)各個(gè)標(biāo)簽對(duì)應(yīng)的BeanDefinitionParser;
- 自定義一個(gè)或多個(gè)解析器,實(shí)現(xiàn) BeanDefinitionParser 接口,用于定義Bean的解析邏輯;
解析流程
有關(guān)于 Spring 對(duì)這部分內(nèi)容的實(shí)現(xiàn)細(xì)節(jié),可以參考 Schema解析,下面我對(duì)這部分內(nèi)容做一個(gè)簡(jiǎn)單的梳理:
- Spring 中對(duì)Bean的解析主要是通過
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
方法,具體的解析邏輯委托給BeanDefinitionParserDelegate
進(jìn)行; -
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
會(huì)區(qū)分 默認(rèn)的Namespace和自定義的Namesapce(除Spring的一些默認(rèn)標(biāo)簽外,其它的都是自定義Namespace) - 在解析自定義Namespace的時(shí)候會(huì)調(diào)用
DefaultNamespaceHandlerResolver#resolve
方法,DefaultNamespaceHandlerResolver
中會(huì)加載所有META-INF/spring.handlers
文件里面的內(nèi)容,然后維護(hù)一套NamespaceURL => NamespaceHandler
的映射關(guān)系。然后在DefaultNamespaceHandlerResolver#resolve
方法中調(diào)用 當(dāng)前NamespaceURL對(duì)應(yīng)的NamespaceHandler#init
方法。 - dubbo對(duì)應(yīng)的 NamespaceHandler 是
DubboNamespaceHandler
,在DubboNamespaceHandler#init
方法中,會(huì)找到各個(gè)標(biāo)簽對(duì)應(yīng)的 BeanDefinitionParser 接口,這里對(duì)應(yīng)DubboBeanDefinitionParser
并緩存起來; - 在解析標(biāo)簽的時(shí)候會(huì)調(diào)用
DubboNamespaceHandler#parse
方法,而真正的解析邏輯委托給內(nèi)部的DubboBeanDefinitionParser#parse
方法;
補(bǔ)充部分關(guān)鍵代碼:
// DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
// 默認(rèn)解析
parseDefaultElement(ele, delegate);
}else {
// 自定義解析
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 自定義解析
delegate.parseCustomElement(root);
}
}
// DubboNamespaceHandler.java
// NamespaceHandlerSupport是一個(gè)抽象類,實(shí)現(xiàn)了NamespaceHandler接口
public class DubboNamespaceHandler extends NamespaceHandlerSupport {
static {
Version.checkDuplicate(DubboNamespaceHandler.class);
}
@Override
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new AnnotationBeanDefinitionParser());
}
}
至此,整個(gè)dubbo的xml標(biāo)簽解析流程就非常清晰了,如果你想通過XML配置的方式來使用dubbo,那么當(dāng)你配置好xml之后,隨著 Spring 的啟動(dòng),就會(huì)自動(dòng)解析dubbo對(duì)應(yīng)的那些標(biāo)簽了。
注解啟動(dòng)
注解是為了讓我們擺脫繁瑣的XML配置,但對(duì)代碼有一定侵入,高版本的dubbo和springboot整合其實(shí)非常方便,引入依賴之后只需要在啟動(dòng)類上添加 @EnableDubbo
注解即可。以 dubbo 2.7.2
和 springboot 2.1.4.RELEASE
為例:
使用示例
依賴
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.dubbo:dubbo:2.7.2'
implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2'
implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2'
implementation 'org.apache.zookeeper:zookeeper:3.4.12'
implementation 'org.apache.curator:curator-recipes:2.12.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
啟動(dòng)類
@SpringBootApplication
@EnableDubbo
public class SDubboApplication {
public static void main(String[] args) {
SpringApplication.run(SDubboApplication.class, args);
}
@Configuration
@PropertySource("classpath:/dubbo-provider.properties")
static class ProviderConfiguration {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://10.9.44.133:2181");
// 注冊(cè)簡(jiǎn)化版的的url到注冊(cè)中心
registryConfig.setSimplified(true);
return registryConfig;
}
@Bean
public MetadataReportConfig metadataReportConfig() {
MetadataReportConfig metadataReportConfig = new MetadataReportConfig();
metadataReportConfig.setAddress("zookeeper://10.9.44.133:2181");
return metadataReportConfig;
}
@Bean
public ConfigCenterConfig configCenterConfig() {
ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
configCenterConfig.setAddress("zookeeper://10.9.44.133:2181");
return configCenterConfig;
}
}
}
dubbo-provider.properties
dubbo.application.name=sdubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882
有關(guān)于通過注解定義Provider和Consumer這里就不介紹了。從上面的代碼中可以看到,那三個(gè)Bean只是一些配置工作,這不是我們關(guān)注的重點(diǎn),重點(diǎn)在 @EnableDubbo
注解,為什么添加這個(gè)注解之后dubbo服務(wù)就自動(dòng)注冊(cè)了?
@EnableDubbo
不妨先看看這個(gè)注解,可以發(fā)現(xiàn)它引用了 @EnableDubboConfig
和 @DubboComponentScan
,前者與配置
相關(guān),后者與 服務(wù)注冊(cè)和服務(wù)引用
相關(guān)。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
......
}
@EnableDubboConfig
這個(gè)注解和 外部化配置相關(guān)
,可以參考一篇博文: 外部化配置
即:一些通用的配置信息全部配置在 application.properties
或者 bootstrap.properties
配置文件中,dubbo會(huì)根據(jù)這些配置信息自動(dòng)創(chuàng)建 ApplicationConfig
、RegistryConfig
、ProviderConfig
等Bean,而不需要我們通過注解的方式硬編碼去創(chuàng)建。
其核心原理在 DubboConfigConfigurationRegistrar
類中,這個(gè)不是本篇文章的重點(diǎn),不過多介紹。
其實(shí)在上面的示例中,就已經(jīng)用到了外部化配置特性,雖然沒有在 application.yaml
中定義dubbo的這些屬性,但是在注解類中通過 @PropertySource("classpath:/dubbo-provider.properties")
將這些屬性導(dǎo)入進(jìn)來了,所以dubbo會(huì)自動(dòng)根據(jù)這些屬性去創(chuàng)建相應(yīng)的Bean, 比如ApplicationConfig
,雖然在示例中沒有通過硬編碼的方式創(chuàng)建ApplicationConfig
,但是dubbo在讀到 dubbo-provider.properties
文件中的 dubbo.application
屬性時(shí)會(huì)自動(dòng)創(chuàng)建一個(gè) ApplicationConfig
。
外部化配置下,dubbo和springboot整合如下:
依賴
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.apache.dubbo:dubbo:2.7.2'
implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2'
implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2'
implementation 'org.apache.zookeeper:zookeeper:3.4.12'
implementation 'org.apache.curator:curator-recipes:2.12.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
application.yml
server:
port: 8786
spring:
main:
allow-bean-definition-overriding: true
dubbo:
application:
name: sdubbo
protocol:
name: dubbo
port: 20882
registry:
address: zookeeper://10.9.44.133:2181
simplified: true
metadata-report:
address: zookeeper://10.9.44.133:2181
config-center:
address: zookeeper://10.9.44.133:2181
啟動(dòng)類
@SpringBootApplication
@EnableDubbo
public class SDubboApplication {
public static void main(String[] args) {
SpringApplication.run(SDubboApplication.class, args);
}
}
@DubboComponentScan
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
......
}
小擴(kuò)展
在 Spring 中,通過 @Import
導(dǎo)入一個(gè)外部類有三種方式
- 直接導(dǎo)入;
@Configuration
@Import(ExternalBean.class)
public class TestImportConfiguration {
}
- 導(dǎo)入一個(gè)
ImportSelector
接口的實(shí)現(xiàn)類,然后重寫selectImports
方法,在該方法中返回要導(dǎo)入類的全類名;
@Configuration
@Import(TestImportSelect.class)
public class TestImportSelectConfiguration {
}
public class TestImportSelect implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.sxy.spring.register.ExternalBean"};
}
}
- 導(dǎo)入一個(gè)
ImportBeanDefinitionRegistrar
接口的實(shí)現(xiàn)類,然后重寫registerBeanDefinitions
方法,在該方法中通過BeanDefinitionRegistry
注冊(cè) BeanDefinition;
@Configuration
@Import(TestImportBeanDefinitionRegistrar.class)
public class TestImportBeanDefinitionRegistrarCongiguration {
}
public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 注冊(cè)一個(gè)bean, 指定bean name
registry.registerBeanDefinition("externalBean", new RootBeanDefinition(ExternalBean.class));
}
}
DubboComponentScanRegistrar
DubboComponentScanRegistrar
實(shí)現(xiàn)了 ImportBeanDefinitionRegistrar
接口,而在它重寫的 registerBeanDefinitions
方法中做了兩件事:
- 注冊(cè)
ServiceAnnotationBeanPostProcessor
; - 注冊(cè)
ReferenceAnnotationBeanPostProcessor
;
ServiceAnnotationBeanPostProcessor
實(shí)現(xiàn)了 BeanDefinitionRegistryPostProcessor
接口;ReferenceAnnotationBeanPostProcessor
實(shí)現(xiàn)了 InstantiationAwareBeanPostProcessorAdapter
接口。 了解Spring的同學(xué)都知道這是Spring的擴(kuò)展接口。
小擴(kuò)展
-
BeanFactoryPostProcessor
:在實(shí)例化bean之前,可以修改BeanDefinition信息; -
BeanDefinitionRegistryPostProcessor
:BeanFactoryPostProcessor
接口的子類,在BeanFactoryPostProcessor之前執(zhí)行,可用于創(chuàng)建 BeanDefinition; -
BeanPostProcessor
: Bean初始化前后執(zhí)行。 -
InstantiationAwareBeanPostProcessor
:BeanPostProcessor 的子類,實(shí)例化前后執(zhí)行; -
ApplicationContextAwareProcessor
:實(shí)現(xiàn)了BeanPostProcessor,在postProcessBeforeInitialization中注入各種Aware接口;
ServiceAnnotationBeanPostProcessor
以一個(gè)dubbo provider為例
@org.apache.dubbo.config.annotation.Service
public class LeannImpl implements ILearn {
@Override
public String learn(String name) {
return "學(xué)習(xí): " + name;
}
}
在 ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry
的方法中,主要做了兩件事:
- 根據(jù)配置的包掃描路徑找到所有帶有
@org.apache.dubbo.config.annotation.Service
注解的類,為這些類創(chuàng)建BeanDefinition
,然后注冊(cè)到IOC容器中;這部分實(shí)現(xiàn)隱藏在DubboClassPathBeanDefinitionScanner#scan
方法中。 - 為每個(gè)原始類再創(chuàng)建一個(gè)
ServiceBean
類型的BeanDefinition
信息。
即:每一個(gè)dubbo服務(wù)最終會(huì)在IOC容器中對(duì)應(yīng)兩個(gè)Bean,一個(gè)是原始類型,一個(gè)是 ServiceBean
類型, ServiceBean
其實(shí)是一個(gè) FactoryBean
, 是實(shí)現(xiàn)服務(wù)暴露的關(guān)鍵,這里不展開。
以上面的例子為例,最終兩個(gè)Bean對(duì)應(yīng)的BeanName分別為:leannImpl
和 ServiceBean:com.sxy.sdubbo.service.ILearn
ReferenceAnnotationBeanPostProcessor
以一個(gè)dubbo consumer為例
@Component("demoServiceComponent")
public class DemoServiceComponent implements DemoService {
@Reference(timeout = 3000)
private DemoService demoService;
@Override
public String sayHello(String name) {
return demoService.sayHello(name);
}
}
ReferenceAnnotationBeanPostProcessor
主要做了兩件事:
- 創(chuàng)建一個(gè)代理服務(wù);
- 為
DemoServiceComponent
注入值,即DemoServiceComponent.demoService = 代理服務(wù)
;
即: DemoServiceComponent
僅僅代表 Spring 容器中的一個(gè)普通Bean;而 @Reference注解標(biāo)注demoService屬性
最終指向的是動(dòng)態(tài)創(chuàng)建的一個(gè)代理服務(wù),就是通過這個(gè)代理服務(wù)實(shí)現(xiàn)與provider通信。
至此,注解模式下,dubbo服務(wù)注冊(cè)與引用流程已經(jīng)很清晰了,具體的實(shí)現(xiàn)細(xì)節(jié)可以查看源碼。
自動(dòng)化配置
自動(dòng)化配置其實(shí)是springboot提供的一個(gè)特性,其目的就是盡量讓用戶原理各種繁瑣配置, 其核心原理就是讀取 META-INF/spring.factories 中的自動(dòng)化配置類,下面簡(jiǎn)單介紹一下。
Spring的自動(dòng)化配置
啟動(dòng)springboot應(yīng)用的時(shí)候會(huì)添加一個(gè) @SpringBootApplication
注解,而該注解中包含了 @EnableAutoConfiguration
注解,而 @EnableAutoConfiguration
就是實(shí)現(xiàn)自動(dòng)化配置的關(guān)鍵
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
......
}
核心就在 AutoConfigurationImportSelector
類,它實(shí)現(xiàn)了 ImportSelector
接口,其實(shí)主要就做了一件事情:
- 通過
SpringFactoriesLoader#loadFactories
方法加載 classpath 下所有 JAR 文件的 META-INF/spring.factories 文件,然后提取出文件中的所有xxxEnableAutoConfiguration
,這樣就相當(dāng)于將所有的xxxEnableAutoConfiguration
注冊(cè)到 Spring 容器中了。當(dāng)然,這些xxxEnableAutoConfiguration
一般會(huì)結(jié)合各種@Conditional
來判斷是否創(chuàng)建Bean。
dubbo-spring-boot-starter
springboot 項(xiàng)目就是由一個(gè)個(gè) starter 組成的,一個(gè) starter 通常包含了該模塊需要的依賴,通常自動(dòng)化配置也是在 starter 中完成的。
dubbo在springboot應(yīng)用中的自動(dòng)化配置也是通過一個(gè) starter 來完成了,官方Git地址:dubbo-spring-boot-project
那么,在自動(dòng)化配置模式先,dubbo與springboot整合應(yīng)該怎么做? 可以用外部化配置,也可以用注解的方式來配置,這里以注解的方式配置為例:
依賴
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
// 因?yàn)槟壳?2019/07/19)dubbo-spring-boot-starter最新只有2.7.1版本
implementation 'org.apache.dubbo:dubbo-spring-boot-starter:2.7.1'
implementation 'org.apache.dubbo:dubbo:2.7.2'
implementation 'org.apache.dubbo:dubbo-registry-zookeeper:2.7.2'
implementation 'org.apache.dubbo:dubbo-metadata-report-zookeeper:2.7.2'
implementation 'org.apache.zookeeper:zookeeper:3.4.12'
implementation 'org.apache.curator:curator-recipes:2.12.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
啟動(dòng)類
// @EnableDubbo 不需要加這個(gè)注解
@SpringBootApplication
public class SDubboApplication {
public static void main(String[] args) {
SpringApplication.run(SDubboApplication.class, args);
}
@Configuration
@PropertySource("classpath:/dubbo-provider.properties")
static class ProviderConfiguration {
@Bean
public RegistryConfig registryConfig() {
RegistryConfig registryConfig = new RegistryConfig();
registryConfig.setAddress("zookeeper://10.9.44.133:2181");
// 注冊(cè)簡(jiǎn)化版的的url到注冊(cè)中心
registryConfig.setSimplified(true);
return registryConfig;
}
@Bean
public MetadataReportConfig metadataReportConfig() {
MetadataReportConfig metadataReportConfig = new MetadataReportConfig();
metadataReportConfig.setAddress("zookeeper://10.9.44.133:2181");
return metadataReportConfig;
}
@Bean
public ConfigCenterConfig configCenterConfig() {
ConfigCenterConfig configCenterConfig = new ConfigCenterConfig();
configCenterConfig.setAddress("zookeeper://10.9.44.133:2181");
return configCenterConfig;
}
}
}
dubbo-provider.properties
dubbo.application.name=sdubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882
# 多了一個(gè)包掃描,在當(dāng)前 starter 版本中,這個(gè)屬性必須配置
dubbo.scan.base-packages =com.sxy.sdubbo
與注解方式的區(qū)別:
- 添加
dubbo-spring-boot-starter
依賴; - 不需要配置
@EnableDubbo
注解; - 需要配置包掃描路徑
dubbo.scan.base-packages
;
其實(shí)現(xiàn)原理就是springboot中的自動(dòng)化配置:
dubbo-spring-boot-starter
的pom.xml文件中引入了 dubbo-spring-boot-autoconfigure
模塊, dubbo-spring-boot-autoconfigure
的pom.xml文件中引入了 dubbo-spring-boot-autoconfigure-compatible
模塊;
- 在
dubbo-spring-boot-autoconfigure
模塊中,META-INF/spring.factories 文件內(nèi)容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
- 在
dubbo-spring-boot-autoconfigure-compatible
模塊中,META-INF/spring.factories 文件內(nèi)容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.apache.dubbo.spring.boot.autoconfigure.DubboAutoConfiguration,\
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBindingAutoConfiguration
org.springframework.context.ApplicationListener=\
org.apache.dubbo.spring.boot.context.event.OverrideDubboConfigApplicationListener,\
org.apache.dubbo.spring.boot.context.event.WelcomeLogoApplicationListener,\
org.apache.dubbo.spring.boot.context.event.AwaitingNonWebApplicationListener
org.springframework.boot.env.EnvironmentPostProcessor=\
org.apache.dubbo.spring.boot.env.DubboDefaultPropertiesEnvironmentPostProcessor
org.springframework.context.ApplicationContextInitializer=\
org.apache.dubbo.spring.boot.context.DubboApplicationContextInitializer
DubboRelaxedBindingAutoConfiguration
主要是和屬性解析有關(guān),這里不做介紹;核心還是 DubboAutoConfiguration
, 在該類中有一下兩段關(guān)鍵代碼
/**
* @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) 強(qiáng)制要求了我們需要配置包掃描路徑,否則該Bean不會(huì)被創(chuàng)建
* Creates {@link ServiceAnnotationBeanPostProcessor} Bean
*
* @param propertyResolver {@link PropertyResolver} Bean
* @return {@link ServiceAnnotationBeanPostProcessor}
*/
@ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME)
@ConditionalOnBean(name = BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME)
@Bean
public ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(
@Qualifier(BASE_PACKAGES_PROPERTY_RESOLVER_BEAN_NAME) PropertyResolver propertyResolver) {
Set<String> packagesToScan = propertyResolver.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());
return new ServiceAnnotationBeanPostProcessor(packagesToScan);
}
/**
* Creates {@link ReferenceAnnotationBeanPostProcessor} Bean if Absent bean工廠不存在referenceAnnotationBeanPostProcessor時(shí)創(chuàng)建
*
* @return {@link ReferenceAnnotationBeanPostProcessor}
*/
@ConditionalOnMissingBean
@Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME)
public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {
return new ReferenceAnnotationBeanPostProcessor();
}
其實(shí)核心就是通過自動(dòng)化的方式創(chuàng)建了 ServiceAnnotationBeanPostProcessor
和 ReferenceAnnotationBeanPostProcessor
,沒有通過 @EnableDubbo
注解觸發(fā)。但是感覺這樣不太好用。