Dubbo源码之Spring整合

2019-12-13 16:11:28 浏览数 (1)

本文主要介绍了在不同的配置模式下,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 方法;

补充部分关键代码:

代码语言: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.2springboot 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 ,前者与配置相关,后者与 服务注册和服务引用相关。

代码语言:javascript复制
@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整合如下:

依赖

代码语言: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 导入一个外部类有三种方式

  1. 直接导入;
代码语言:javascript复制
@Configuration
@Import(ExternalBean.class)
public class TestImportConfiguration {
}
  1. 导入一个 ImportSelector 接口的实现类,然后重写 selectImports 方法,在该方法中返回要导入类的全类名;
代码语言:javascript复制
@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;
代码语言:javascript复制
@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为例

代码语言:javascript复制
@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为例

代码语言: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 主要做了两件事:

  1. 创建一个代理服务;
  2. DemoServiceComponent 注入值,即 DemoServiceComponent.demoService = 代理服务;

即: DemoServiceComponent 仅仅代表 Spring 容器中的一个普通Bean;而 @Reference注解标注demoService属性 最终指向的是动态创建的一个代理服务,就是通过这个代理服务实现与provider通信。

至此,注解模式下,dubbo服务注册与引用流程已经很清晰了,具体的实现细节可以查看源码。

自动化配置

自动化配置其实是springboot提供的一个特性,其目的就是尽量让用户原理各种繁琐配置, 其核心原理就是读取 META-INF/spring.factories 中的自动化配置类,下面简单介绍一下。

Spring的自动化配置

启动springboot应用的时候会添加一个 @SpringBootApplication 注解,而该注解中包含了 @EnableAutoConfiguration 注解,而 @EnableAutoConfiguration 就是实现自动化配置的关键

代码语言:javascript复制
@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整合应该怎么做? 可以用外部化配置,也可以用注解的方式来配置,这里以注解的方式配置为例:

依赖

代码语言: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

与注解方式的区别:

  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 文件内容如下:
代码语言:javascript复制
org.springframework.boot.autoconfigure.EnableAutoConfiguration=
org.apache.dubbo.spring.boot.autoconfigure.DubboRelaxedBinding2AutoConfiguration
  1. dubbo-spring-boot-autoconfigure-compatible 模块中,META-INF/spring.factories 文件内容如下:
代码语言:javascript复制
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 , 在该类中有一下两段关键代码

代码语言:javascript复制
/**
 * @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 注解触发。但是感觉这样不太好用。

0 人点赞