本文主要介绍了在不同的配置模式下,dubbo与spring整合的原理,即:xml配置、注解配置、自动化配置 三种模式下的配置生效原理。
Spring提供了 Schema 扩展机制,用户可以自定义 Schema 文件,并自定义 Schema 解析器,然后集成到SpringIOC容器中。 创建自定义扩展,主要有以下步骤:
有关于 Spring 对这部分内容的实现细节,可以参考Schema解析,下面我对这部分内容做一个简单的梳理:
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
方法,具体的解析逻辑委托给 BeanDefinitionParserDelegate
进行; DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
会区分 默认的Namespace和自定义的Namesapce(除Spring的一些默认标签外,其它的都是自定义Namespace) DefaultNamespaceHandlerResolver#resolve
方法, DefaultNamespaceHandlerResolver
中会加载所有 META-INF/spring.handlers
文件里面的内容,然后维护一套 NamespaceURL => NamespaceHandler
的映射关系。然后在 DefaultNamespaceHandlerResolver#resolve
方法中调用 当前NamespaceURL对应的 NamespaceHandler#init
方法。 DubboNamespaceHandler
,在 DubboNamespaceHandler#init
方法中,会找到各个标签对应的 BeanDefinitionParser 接口,这里对应 DubboBeanDefinitionParser
并缓存起来; DubboNamespaceHandler#parse
方法,而真正的解析逻辑委托给内部的 DubboBeanDefinitionParser#parse
方法; 补充部分关键代码:
// 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)) { // 默认解析 parseDefaultElement(ele, delegate); }else { // 自定义解析 delegate.parseCustomElement(ele); } } } } else { // 自定义解析 delegate.parseCustomElement(root); } } 复制代码
// DubboNamespaceHandler.java // NamespaceHandlerSupport是一个抽象类,实现了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()); } } 复制代码
至此,整个dubbo的xml标签解析流程就非常清晰了,如果你想通过XML配置的方式来使用dubbo,那么当你配置好xml之后,随着 Spring 的启动,就会自动解析dubbo对应的那些标签了。
注解是为了让我们摆脱繁琐的XML配置,但对代码有一定侵入,高版本的dubbo和springboot整合其实非常方便,引入依赖之后只需要在启动类上添加 @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' } 复制代码
@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"); // 注册简化版的的url到注册中心 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.application.name=sdubbo dubbo.protocol.name=dubbo dubbo.protocol.port=20882 复制代码
有关于通过注解定义Provider和Consumer这里就不介绍了。从上面的代码中可以看到,那三个Bean只是一些配置工作,这不是我们关注的重点,重点在 @EnableDubbo
注解,为什么添加这个注解之后dubbo服务就自动注册了?
不妨先看看这个注解,可以发现它引用了 @EnableDubboConfig
和 @DubboComponentScan
,前者与 配置
相关,后者与 服务注册和服务引用
相关。
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented @EnableDubboConfig @DubboComponentScan public @interface EnableDubbo { ...... } 复制代码
这个注解和 外部化配置相关
,可以参考一篇博文:外部化配置
即:一些通用的配置信息全部配置在 application.properties
或者 bootstrap.properties
配置文件中,dubbo会根据这些配置信息自动创建 ApplicationConfig
、 RegistryConfig
、 ProviderConfig
等Bean,而不需要我们通过注解的方式硬编码去创建。
其核心原理在 DubboConfigConfigurationRegistrar
类中,这个不是本篇文章的重点,不过多介绍。
其实在上面的示例中,就已经用到了外部化配置特性,虽然没有在 application.yaml
中定义dubbo的这些属性,但是在注解类中通过 @PropertySource("classpath:/dubbo-provider.properties")
将这些属性导入进来了,所以dubbo会自动根据这些属性去创建相应的Bean, 比如 ApplicationConfig
,虽然在示例中没有通过硬编码的方式创建 ApplicationConfig
,但是dubbo在读到 dubbo-provider.properties
文件中的 dubbo.application
属性时会自动创建一个 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' } 复制代码
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 复制代码
@SpringBootApplication @EnableDubbo public class SDubboApplication { public static void main(String[] args) { SpringApplication.run(SDubboApplication.class, args); } } 复制代码
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(DubboComponentScanRegistrar.class) public @interface DubboComponentScan { ...... } 复制代码
小扩展
@Import
导入一个外部类有三种方式 @Configuration @Import(ExternalBean.class) public class TestImportConfiguration { } 复制代码
ImportSelector
接口的实现类,然后重写 selectImports
方法,在该方法中返回要导入类的全类名; @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"}; } } 复制代码
ImportBeanDefinitionRegistrar
接口的实现类,然后重写 registerBeanDefinitions
方法,在该方法中通过 BeanDefinitionRegistry
注册 BeanDefinition; @Configuration @Import(TestImportBeanDefinitionRegistrar.class) public class TestImportBeanDefinitionRegistrarCongiguration { } public class TestImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // 注册一个bean, 指定bean name registry.registerBeanDefinition("externalBean", new RootBeanDefinition(ExternalBean.class)); } } 复制代码
DubboComponentScanRegistrar
实现了 ImportBeanDefinitionRegistrar
接口,而在它重写的 registerBeanDefinitions
方法中做了两件事:
ServiceAnnotationBeanPostProcessor ReferenceAnnotationBeanPostProcessor
ServiceAnnotationBeanPostProcessor
实现了 BeanDefinitionRegistryPostProcessor
接口; ReferenceAnnotationBeanPostProcessor
实现了 InstantiationAwareBeanPostProcessorAdapter
接口。 了解Spring的同学都知道这是Spring的扩展接口。
小扩展
BeanFactoryPostProcessor
:在实例化bean之前,可以修改BeanDefinition信息; BeanDefinitionRegistryPostProcessor
: BeanFactoryPostProcessor
接口的子类,在BeanFactoryPostProcessor之前执行,可用于创建 BeanDefinition; BeanPostProcessor
: Bean初始化前后执行。 InstantiationAwareBeanPostProcessor
:BeanPostProcessor 的子类,实例化前后执行; ApplicationContextAwareProcessor
:实现了BeanPostProcessor,在postProcessBeforeInitialization中注入各种Aware接口; 以一个dubbo provider为例
@org.apache.dubbo.config.annotation.Service public class LeannImpl implements ILearn { @Override public String learn(String name) { return "学习: " + name; } } 复制代码
在 ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry
的方法中,主要做了两件事:
@org.apache.dubbo.config.annotation.Service
注解的类,为这些类创建 BeanDefinition
,然后注册到IOC容器中;这部分实现隐藏在 DubboClassPathBeanDefinitionScanner#scan
方法中。 ServiceBean
类型的 BeanDefinition
信息。
即:每一个dubbo服务最终会在IOC容器中对应两个Bean,一个是原始类型,一个是 ServiceBean
类型, ServiceBean
其实是一个 FactoryBean
, 是实现服务暴露的关键,这里不展开。
以上面的例子为例,最终两个Bean对应的BeanName分别为: leannImpl
和 ServiceBean:com.sxy.sdubbo.service.ILearn
以一个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
主要做了两件事:
DemoServiceComponent
注入值,即 DemoServiceComponent.demoService = 代理服务
;
即: DemoServiceComponent
仅仅代表 Spring 容器中的一个普通Bean;而 @Reference注解标注demoService属性
最终指向的是动态创建的一个代理服务,就是通过这个代理服务实现与provider通信。
至此,注解模式下,dubbo服务注册与引用流程已经很清晰了,具体的实现细节可以查看源码。
自动化配置其实是springboot提供的一个特性,其目的就是尽量让用户原理各种繁琐配置, 其核心原理就是读取 META-INF/spring.factories 中的自动化配置类,下面简单介绍一下。
启动springboot应用的时候会添加一个 @SpringBootApplication
注解,而该注解中包含了 @EnableAutoConfiguration
注解,而 @EnableAutoConfiguration
就是实现自动化配置的关键
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(AutoConfigurationImportSelector.class) public @interface EnableAutoConfiguration { ...... } 复制代码
核心就在 AutoConfigurationImportSelector
类,它实现了 ImportSelector
接口,其实主要就做了一件事情:
SpringFactoriesLoader#loadFactories
方法加载 classpath 下所有 JAR 文件的 META-INF/spring.factories 文件,然后提取出文件中的所有 xxxEnableAutoConfiguration
,这样就相当于将所有的 xxxEnableAutoConfiguration
注册到 Spring 容器中了。当然,这些 xxxEnableAutoConfiguration
一般会结合各种 @Conditional
来判断是否创建Bean。 springboot 项目就是由一个个 starter 组成的,一个 starter 通常包含了该模块需要的依赖,通常自动化配置也是在 starter 中完成的。
dubbo在springboot应用中的自动化配置也是通过一个 starter 来完成了,官方Git地址: dubbo-spring-boot-project
那么,在自动化配置模式先,dubbo与springboot整合应该怎么做? 可以用外部化配置,也可以用注解的方式来配置,这里以注解的方式配置为例:
dependencies { implementation 'org.springframework.boot:spring-boot-starter' implementation 'org.springframework.boot:spring-boot-starter-web' // 因为目前(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' } 复制代码
// @EnableDubbo 不需要加这个注解 @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"); // 注册简化版的的url到注册中心 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.application.name=sdubbo dubbo.protocol.name=dubbo dubbo.protocol.port=20882 # 多了一个包扫描,在当前 starter 版本中,这个属性必须配置 dubbo.scan.base-packages =com.sxy.sdubbo 复制代码
与注解方式的区别:
dubbo-spring-boot-starter @EnableDubbo dubbo.scan.base-packages
其实现原理就是springboot中的自动化配置: 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 文件内容如下: org.springframework.boot.autoconfigure.EnableAutoConfiguration=/ org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration 复制代码
dubbo-spring-boot-autoconfigure-compatible
模块中,META-INF/spring.factories 文件内容如下: 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
主要是和属性解析有关,这里不做介绍;核心还是 DubboAutoConfiguration
, 在该类中有一下两段关键代码
/** * @ConditionalOnProperty(prefix = DUBBO_SCAN_PREFIX, name = BASE_PACKAGES_PROPERTY_NAME) 强制要求了我们需要配置包扫描路径,否则该Bean不会被创建 * 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时创建 * * @return {@link ReferenceAnnotationBeanPostProcessor} */ @ConditionalOnMissingBean @Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME) public ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() { return new ReferenceAnnotationBeanPostProcessor(); } 复制代码
其实核心就是通过自动化的方式创建了 ServiceAnnotationBeanPostProcessor
和 ReferenceAnnotationBeanPostProcessor
,没有通过 @EnableDubbo
注解触发。但是感觉这样不太好用。