本文主要介绍了在不同的配置模式下,dubbo与spring整合的原理,即:xml配置、注解配置、自动化配置 三种模式下的配置生效原理。
XML启动
Schema扩展机制
Spring提供了 Schema 扩展机制,用户可以自定义 Schema 文件,并自定义 Schema 解析器,然后集成到SpringIOC容器中。
创建自定义扩展,主要有以下步骤:
- 创建 Schema 文件,描述自定义的合法构建模块,也就是xsd文件,主要用于定义数据约束;
- 自定义个处理器类,并实现NamespaceHandler接口,在里面注册各个标签对应的BeanDefinitionParser;
- 自定义一个或多个解析器,实现 BeanDefinitionParser 接口,用于定义Bean的解析逻辑;
解析流程
有关于 Spring 对这部分内容的实现细节,可以参考 Schema解析,下面我对这部分内容做一个简单的梳理:
- Spring 中对Bean的解析主要是通过
DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
方法,具体的解析逻辑委托给BeanDefinitionParserDelegate
进行; DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
会区分 默认的Namespace和自定义的Namesapce(除Spring的一些默认标签外,其它的都是自定义Namespace)- 在解析自定义Namespace的时候会调用
DefaultNamespaceHandlerResolver#resolve
方法,DefaultNamespaceHandlerResolver
中会加载所有META-INF/spring.handlers
文件里面的内容,然后维护一套NamespaceURL => NamespaceHandler
的映射关系。然后在DefaultNamespaceHandlerResolver#resolve
方法中调用 当前NamespaceURL对应的NamespaceHandler#init
方法。 - dubbo对应的 NamespaceHandler 是
DubboNamespaceHandler
,在DubboNamespaceHandler#init
方法中,会找到各个标签对应的 BeanDefinitionParser 接口,这里对应DubboBeanDefinitionParser
并缓存起来; - 在解析标签的时候会调用
DubboNamespaceHandler#parse
方法,而真正的解析逻辑委托给内部的DubboBeanDefinitionParser#parse
方法;
补充部分关键代码:
代码语言:javascript复制// 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);
}
}
代码语言:javascript复制// 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
为例:
使用示例
依赖
代码语言:javascript复制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'
}
启动类
代码语言:javascript复制@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-provider.properties
代码语言:javascript复制dubbo.application.name=sdubbo
dubbo.protocol.name=dubbo
dubbo.protocol.port=20882
有关于通过注解定义Provider和Consumer这里就不介绍了。从上面的代码中可以看到,那三个Bean只是一些配置工作,这不是我们关注的重点,重点在 @EnableDubbo
注解,为什么添加这个注解之后dubbo服务就自动注册了?
@EnableDubbo
不妨先看看这个注解,可以发现它引用了 @EnableDubboConfig
和 @DubboComponentScan
,前者与配置
相关,后者与 服务注册和服务引用
相关。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@EnableDubboConfig
@DubboComponentScan
public @interface EnableDubbo {
......
}
@EnableDubboConfig
这个注解和 外部化配置相关
,可以参考一篇博文: 外部化配置
即:一些通用的配置信息全部配置在 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整合如下:
依赖
代码语言:javascript复制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
代码语言:javascript复制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
启动类
代码语言:javascript复制@SpringBootApplication
@EnableDubbo
public class SDubboApplication {
public static void main(String[] args) {
SpringApplication.run(SDubboApplication.class, args);
}
}
@DubboComponentScan
代码语言:javascript复制@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(DubboComponentScanRegistrar.class)
public @interface DubboComponentScan {
......
}
小扩展
在 Spring 中,通过 @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
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接口;
ServiceAnnotationBeanPostProcessor
以一个dubbo provider为例
代码语言:javascript复制@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
ReferenceAnnotationBeanPostProcessor
以一个dubbo consumer为例
代码语言:javascript复制@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 中的自动化配置类,下面简单介绍一下。
Spring的自动化配置
启动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。
dubbo-spring-boot-starter
springboot 项目就是由一个个 starter 组成的,一个 starter 通常包含了该模块需要的依赖,通常自动化配置也是在 starter 中完成的。
dubbo在springboot应用中的自动化配置也是通过一个 starter 来完成了,官方Git地址:dubbo-spring-boot-project
那么,在自动化配置模式先,dubbo与springboot整合应该怎么做? 可以用外部化配置,也可以用注解的方式来配置,这里以注解的方式配置为例:
依赖
代码语言:javascript复制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'
}
启动类
代码语言:javascript复制// @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-provider.properties
代码语言:javascript复制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
注解触发。但是感觉这样不太好用。