Dubbo源码之Spring整合

  • 2019 年 12 月 13 日
  • 笔记

本文主要介绍了在不同的配置模式下,dubbo与spring整合的原理,即:xml配置、注解配置、自动化配置 三种模式下的配置生效原理。

XML启动

Schema扩展机制

Spring提供了 Schema 扩展机制,用户可以自定义 Schema 文件,并自定义 Schema 解析器,然后集成到SpringIOC容器中。 创建自定义扩展,主要有以下步骤:

  1. 创建 Schema 文件,描述自定义的合法构建模块,也就是xsd文件,主要用于定义数据约束;
  2. 自定义个处理器类,并实现NamespaceHandler接口,在里面注册各个标签对应的BeanDefinitionParser;
  3. 自定义一个或多个解析器,实现 BeanDefinitionParser 接口,用于定义Bean的解析逻辑;

解析流程

有关于 Spring 对这部分内容的实现细节,可以参考 Schema解析,下面我对这部分内容做一个简单的梳理:

  1. Spring 中对Bean的解析主要是通过 DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 方法,具体的解析逻辑委托给 BeanDefinitionParserDelegate 进行;
  2. DefaultBeanDefinitionDocumentReader#parseBeanDefinitions 会区分 默认的Namespace和自定义的Namesapce(除Spring的一些默认标签外,其它的都是自定义Namespace)
  3. 在解析自定义Namespace的时候会调用 DefaultNamespaceHandlerResolver#resolve 方法, DefaultNamespaceHandlerResolver 中会加载所有 META-INF/spring.handlers 文件里面的内容,然后维护一套 NamespaceURL => NamespaceHandler 的映射关系。然后在 DefaultNamespaceHandlerResolver#resolve 方法中调用 当前NamespaceURL对应的 NamespaceHandler#init 方法。
  4. dubbo对应的 NamespaceHandler 是 DubboNamespaceHandler,在 DubboNamespaceHandler#init 方法中,会找到各个标签对应的 BeanDefinitionParser 接口,这里对应 DubboBeanDefinitionParser 并缓存起来;
  5. 在解析标签的时候会调用 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.2springboot 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-provider.properties

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会根据这些配置信息自动创建 ApplicationConfigRegistryConfigProviderConfig 等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'  }

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

启动类

@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 {  ......  }

小扩展

在 Spring 中,通过 @Import 导入一个外部类有三种方式

  1. 直接导入;
@Configuration  @Import(ExternalBean.class)  public class TestImportConfiguration {  }
  1. 导入一个 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"};      }  }
  1. 导入一个 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 方法中做了两件事:

  1. 注册 ServiceAnnotationBeanPostProcessor;
  2. 注册 ReferenceAnnotationBeanPostProcessor;

ServiceAnnotationBeanPostProcessor 实现了 BeanDefinitionRegistryPostProcessor 接口;ReferenceAnnotationBeanPostProcessor 实现了 InstantiationAwareBeanPostProcessorAdapter 接口。 了解Spring的同学都知道这是Spring的扩展接口。

小扩展

  1. BeanFactoryPostProcessor:在实例化bean之前,可以修改BeanDefinition信息;
  2. BeanDefinitionRegistryPostProcessor: BeanFactoryPostProcessor 接口的子类,在BeanFactoryPostProcessor之前执行,可用于创建 BeanDefinition;
  3. BeanPostProcessor: Bean初始化前后执行。
  4. InstantiationAwareBeanPostProcessor:BeanPostProcessor 的子类,实例化前后执行;
  5. ApplicationContextAwareProcessor:实现了BeanPostProcessor,在postProcessBeforeInitialization中注入各种Aware接口;
ServiceAnnotationBeanPostProcessor

以一个dubbo provider为例

@org.apache.dubbo.config.annotation.Service  public class LeannImpl implements ILearn {      @Override      public String learn(String name) {          return "学习: " + name;      }  }

ServiceAnnotationBeanPostProcessor#postProcessBeanDefinitionRegistry 的方法中,主要做了两件事:

  1. 根据配置的包扫描路径找到所有带有 @org.apache.dubbo.config.annotation.Service 注解的类,为这些类创建 BeanDefinition ,然后注册到IOC容器中;这部分实现隐藏在 DubboClassPathBeanDefinitionScanner#scan 方法中。
  2. 为每个原始类再创建一个 ServiceBean 类型的 BeanDefinition 信息。

即:每一个dubbo服务最终会在IOC容器中对应两个Bean,一个是原始类型,一个是 ServiceBean 类型, ServiceBean 其实是一个 FactoryBean , 是实现服务暴露的关键,这里不展开。 以上面的例子为例,最终两个Bean对应的BeanName分别为:leannImplServiceBean:com.sxy.sdubbo.service.ILearn

ReferenceAnnotationBeanPostProcessor

以一个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 主要做了两件事:

  1. 创建一个代理服务;
  2. 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 接口,其实主要就做了一件事情:

  1. 通过 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整合应该怎么做? 可以用外部化配置,也可以用注解的方式来配置,这里以注解的方式配置为例:

依赖

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-provider.properties

dubbo.application.name=sdubbo  dubbo.protocol.name=dubbo  dubbo.protocol.port=20882  # 多了一个包扫描,在当前 starter 版本中,这个属性必须配置  dubbo.scan.base-packages =com.sxy.sdubbo

与注解方式的区别:

  1. 添加 dubbo-spring-boot-starter 依赖;
  2. 不需要配置 @EnableDubbo 注解;
  3. 需要配置包扫描路径 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 模块;

  1. dubbo-spring-boot-autoconfigure 模块中,META-INF/spring.factories 文件内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=  org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
  1. 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();  }

其实核心就是通过自动化的方式创建了 ServiceAnnotationBeanPostProcessorReferenceAnnotationBeanPostProcessor,没有通过 @EnableDubbo 注解触发。但是感觉这样不太好用。