文章

外部化配置-集成SpringBoot

外部化配置-集成SpringBoot

本文实际是 《Dubbo 源码分析 —— 集成 Spring Boot》文章。考虑到和 Dubbo 配置比较相关,所以改成这个标题。

1. 概述

本文,我们来分享 https://github.com/apache/incubator-dubbo-spring-boot-project 项目的源码解析,看看 Dubbo 是如何集成到 Spring Boot 中的。

在阅读本文之前,希望胖友能够先熟读 中文文档 。最好呢,当然不强制,可以操练下每个 Demo 。

2. 调试环境搭建

在读源码之前,我们当然是先把调试环境搭建起来。

2.1 依赖工具

  • JDK :1.8+
  • Maven
  • IntelliJ IDEA

2.2 源码拉取

从官方仓库 https://github.com/apache/incubator-dubbo-spring-boot-projectFork 出属于自己的仓库。为什么要 Fork ?既然开始阅读、调试源码,我们可能会写一些注释,有了自己的仓库,可以进行自由的提交。

使用 IntelliJ IDEA 从 Fork 出来的仓库拉取代码。拉取完成后,Maven 会下载依赖包,可能会花费一些时间,耐心等待下。


在等待的过程中,我来简单说下,搭建调试环境的过程:

  1. 启动 Dubbo Provider
  2. 启动 Dubbo Consumer

考虑到方便,我们直接使用 dubbo-registry-zookeeper-samples 项目提供的示例。

另外,本文使用的 incubator-dubbo-spring-boot-project 版本是 0.2.1 。

2.3 启动 Dubbo Provider

右键运行

dubbo-registry-zookeeper-samples

项目下的

provider-sample

的 DubboRegistryZooKeeperProviderBootstrap 的

#main(String[] args)

方法,Provider 就启动完成了。输出日志如下图:

日志

这个示例比较有意思的是,提供了 EmbeddedZooKeeper 类,用于启动内嵌的 Zookeeper 。

2.4 启动 Dubbo Consumer

右键运行

dubbo-registry-zookeeper-samples

项目下的

consumer-sample

的 DubboRegistryZooKeeperConsumerBootstrap 的

#main(String[] args)

方法,Consumer 就启动完成了。输出日志如下图:

日志

因为 DubboRegistryZooKeeperConsumerBootstrap 的 Spring Boot 启动调用如下:

1
// DubboRegistryZooKeeperConsumerBootstrap.javaprivate final Logger logger = LoggerFactory.getLogger(getClass());@Reference(version = "${demo.service.version}")private DemoService demoService;@Beanpublic ApplicationRunner runner() {    return args -> logger.info(demoService.sayHello("mercyblitz")); // 发起调用}public static void main(String[] args) {    SpringApplication.run(DubboRegistryZooKeeperConsumerBootstrap.class) // 启动    .close(); // <X> 关闭}
  • 处,所以在发起一次 Dubbo 调用之后,会直接关闭 Spring Boot 应用。因此,JVM 进程就直接结束了。

3. 项目结构一览

本文主要分享 incubator-dubbo-spring-boot-project 的 项目结构。希望通过本文能让胖友对 incubator-dubbo-spring-boot-project 的整体项目有个简单的了解。

项目结构一览

3.1 代码统计

这里先分享一个小技巧。笔者在开始源码学习时,会首先了解项目的代码量。

第一种方式,使用 IDEA Statistic 插件,统计整体代码量。

Statistic 统计代码量

我们可以粗略的看到,总的代码量在 2000 行。这其中还包括单元测试,示例等等代码。所以,不慌,一点不慌~

第二种方式,使用 Shell 脚本命令逐个 Maven 模块统计

一般情况下,笔者使用 find . -name “*.java”xargs catgrep -v -e ^$ -e ^//.*$wc -l 。这个命令只过滤了部分注释,所以相比 IDEA Statistic偏多

当然,考虑到准确性,胖友需要手动 cd 到每个 Maven 项目的 src/main/java 目录下,以达到排除单元测试的代码量。

Shell 脚本统计代码量

统计完后,是不是更加不慌了。哈哈哈哈。

3.2 dubbo-spring-boot-parent 模块

dubbo-spring-boot-parent 模块,无具体代码,作为其它项目的 Maven Parent 项目,例如定义了依赖版本号。

3.3 dubbo-spring-boot-starter 模块

dubbo-spring-boot-starter 模块,无具体代码,作为 Spring Boot Dubbo Starter 模块。其 pom.xml 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<project xmlns="http://maven.apache.org/POM/4.0.0"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <parent>
    <groupId>com.alibaba.boot</groupId>
    <artifactId>dubbo-spring-boot-parent</artifactId>
    <version>${revision}</version>
    <relativePath>../dubbo-spring-boot-parent</relativePath>
  </parent>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>dubbo-spring-boot-starter</artifactId>
  <packaging>jar</packaging>
  <name>Dubbo Spring Boot Starter</name>
  <description>Dubbo Spring Boot Starter</description>
  <dependencies>
    <!-- Spring Boot dependencies -->    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
      <optional>true</optional>
    </dependency>
    <!-- ZK -->    <dependency>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.apache.curator</groupId>
      <artifactId>curator-framework</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>com.alibaba.boot</groupId>
      <artifactId>dubbo-spring-boot-autoconfigure</artifactId>
      <version>${revision}</version>
    </dependency>
  </dependencies>
</project>

3.4 dubbo-spring-boot-distribution 模块

dubbo-spring-boot-distribution 模块,无具体代码,用于 Spring Boot Dubbo 使用 maven-assembly-plugin 插件,打包出我们后续使用的 Releases

3.5 dubbo-spring-boot-autoconfigure 模块

dubbo-spring-boot-autoconfigure 模块,754 行代码,提供了 Spring Boot Dubbo 的自动配置(AutoConfigure)的具体实现。

3.6 dubbo-spring-boot-actuator 模块

dubbo-spring-boot-actuator 模块,782 行代码,提供了 Spring Boot Dubbo 的 Actuator 的具体实现。

Spring Boot Actuator 的关键特性,是在应用程序里提供众多 HTTP 接口,通过它们了解应用程序运行时的内部状况。

3.7 dubbo-spring-boot-samples 模块

dubbo-spring-boot-samples 模块,708 行代码,提供了四个示例。比较重点可以看的是两个:

  • dubbo-registry-zookeeper-samples 模块,提供基于 Zookeeper 作为注册中心的使用示例。
  • externalized-configuration-samples 模块,提供了外部化配置的示例。

3.8 小结

貌似也没啥好小结的。想要偷懒的话,只要看 dubbo-spring-boot-autoconfigure 模块,一共是 754 行代码。哈哈哈~

当然,真的要深入的话,还是要看 Dubbo 本身的代码的。

4. dubbo-spring-boot-autoconfigure 源码

dubbo-spring-boot-autoconfigure

模块,所有类如下图:

dubbo-spring-boot-autoconfigure 模块

如下开始,是 autoconfigure 包下。

4.1 DubboAutoConfiguration

com.alibaba.boot.dubbo.autoconfigure.DubboAutoConfiguration ,Dubbo 自动配置类。代码如下:

1
// DubboAutoConfiguration.java@Configuration // 配置类@ConditionalOnProperty(prefix = DUBBO_PREFIX, name = "enabled", matchIfMissing = true, havingValue = "true") // 要求配置了 "dubbo.enabled=true" 或者,"dubbo.enabled" 未配置@ConditionalOnClass(AbstractConfig.class) // AbstractConfig 类存在的时候,即用于判断有 Dubbo 库public class DubboAutoConfiguration {    // ... 省略其它代码}
  • 类上的每个注解的作用,请看其后的注释。

4.1.1 serviceAnnotationBeanPostProcessor

#serviceAnnotationBeanPostProcessor() 方法,创建 ServiceAnnotationBeanPostProcessor Bean 对象。代码如下:

1
// DubboAutoConfiguration.java@ConditionalOnProperty(name = BASE_PACKAGES_PROPERTY_NAME) // 配置了 "dubbo.scan.base-package" 属性,即要扫描 Dubbo 注解的包@ConditionalOnClass(ConfigurationPropertySources.class) // 有 Spring Boot 配置加载的功能@Beanpublic ServiceAnnotationBeanPostProcessor serviceAnnotationBeanPostProcessor(Environment environment) {    // <1> 获得 "dubbo.scan.base-package" 属性    Set<String> packagesToScan = environment.getProperty(BASE_PACKAGES_PROPERTY_NAME, Set.class, emptySet());    // <2> 创建 ServiceAnnotationBeanPostProcessor 对象    return new ServiceAnnotationBeanPostProcessor(packagesToScan);}
  • <1> 处,获得 “dubbo.scan.base-package” 属性,即要扫描 Dubbo 注解的包。
  • <2> 处,创建 ServiceAnnotationBeanPostProcessor 对象。后续,ServiceAnnotationBeanPostProcessor 会扫描 packagesToScan 包的 Dubbo @Service 注解,创建对应的 Dubbo Service Bean 对象们。
    • ServiceAnnotationBeanPostProcessor 属于 Dubbo 项目本身,所以本文就不解析逻。

4.1.2 referenceAnnotationBeanPostProcessor

#referenceAnnotationBeanPostProcessor() 方法,创建 Bean 名字为 “referenceAnnotationBeanPostProcessor” 的 ReferenceAnnotationBeanPostProcessor Bean 对象。代码如下:

1
// DubboAutoConfiguration.java@ConditionalOnMissingBean // 不存在 ReferenceAnnotationBeanPostProcessor Bean 的时候@Bean(name = ReferenceAnnotationBeanPostProcessor.BEAN_NAME) // Bean 的名字是 referenceAnnotationBeanPostProcessorpublic ReferenceAnnotationBeanPostProcessor referenceAnnotationBeanPostProcessor() {    return new ReferenceAnnotationBeanPostProcessor();}
  • 后续,ReferenceAnnotationBeanPostProcessor 会扫描 Dubbo @Reference 注解,创建对应的 Dubbo Service Bean 对象们。
    • ReferenceAnnotationBeanPostProcessor 属于 Dubbo 项目本身,所以本文就不解析逻。

4.1.3 relaxedDubboConfigBinder

#relaxedDubboConfigBinder() 方法,创建 RelaxedDubboConfigBinder Bean 对象。代码如下:

1
// DubboAutoConfiguration.java@ConditionalOnClass(Binder.class) // 存在 Binder 类的时候@Bean@Scope(scopeName = SCOPE_PROTOTYPE) // 多例public RelaxedDubboConfigBinder relaxedDubboConfigBinder() {    return new RelaxedDubboConfigBinder();}
  • RelaxedDubboConfigBinder ,用于将具体的属性,设置到相应的 AbstractConfig 对象中。
  • 为什么 多例 @Scope(scopeName = SCOPE_PROTOTYPE) 注解是 呢?因为有多个 AbstractConfig 对象呀~
  • 详细的解析,见 「4.2 RelaxedDubboConfigBinder」

4.1.4 XXXDubboConfigConfiguration

1
2
3
4
5
6
// DubboAutoConfiguration.java/** * Single Dubbo Config Configuration
 * * @see EnableDubboConfig
 * @see DubboConfigConfiguration.Single
 */@EnableDubboConfigprotected static class SingleDubboConfigConfiguration {}/** * Multiple Dubbo Config Configuration , equals @EnableDubboConfig.multiple() == <code>true</code> * * @see EnableDubboConfig
 * @see DubboConfigConfiguration.Multiple
 */@ConditionalOnProperty(name = MULTIPLE_CONFIG_PROPERTY_NAME, havingValue = "true") // 要求配置 "dubbo.config.multiple=true" 。默认情况下,Dubbo 自带 "dubbo.config.multiple=true"@EnableDubboConfig(multiple = true)protected static class MultipleDubboConfigConfiguration {}

```plain text dubbo.application dubbo.module dubbo.registry dubbo.protocol dubbo.monitor dubbo.provider dubbo.consumer

1
2
3
```plain text
    * <font style="color:rgb(51, 51, 51);">~</font>
  • MultipleDubboConfigConfiguration 对应 @EnableDubboConfig(multiple = true) 。
    • 要求配置 “dubbo.config.multiple=true” 。默认情况下,Dubbo 自带 “dubbo.config.multiple=true” ,所以也会创建。
    • 引入了多个 Dubbo 配置绑定 Bean 的配置。即配置文件如下属性:

```plain text dubbo.applications dubbo.modules dubbo.registries dubbo.protocols dubbo.monitors dubbo.providers dubbo.consumers

1
2
3
```plain text
    * <font style="color:rgb(51, 51, 51);">~</font>

4.2 RelaxedDubboConfigBinder

com.alibaba.boot.dubbo.autoconfigure.RelaxedDubboConfigBinder ,继承 AbstractDubboConfigBinder 抽象类,负责将 Spring Boot 的配置属性,注入到 Dubbo AbstractConfig 配置对象中。代码 如下:

1
// RelaxedDubboConfigBinder.java@Overridepublic <C extends AbstractConfig> void bind(String prefix, C dubboConfig) {    // <1.1> 获得 PropertySource 数组    Iterable<PropertySource<?>> propertySources = getPropertySources();    // Converts ConfigurationPropertySources    // <1.2> 转换成 ConfigurationPropertySource 数组    Iterable<ConfigurationPropertySource> configurationPropertySources = from(propertySources);    // Wrap Bindable from DubboConfig instance    // <2> 将 dubboConfig 包装成 Bindable 对象    Bindable<C> bindable = Bindable.ofInstance(dubboConfig);    // <3.1> 创建 Binder 对象    Binder binder = new Binder(configurationPropertySources, new PropertySourcesPlaceholdersResolver(propertySources));    // Get BindHandler    // <3.2> 获得 BindHandler 对象    BindHandler bindHandler = getBindHandler();    // Bind    // <3.3> 执行绑定,会将 propertySources 属性,注入到 dubboConfig 对象中    binder.bind(prefix, bindable, bindHandler);}
  • AbstractDubboConfigBinder 属于 Dubbo 项目本身,所以本文就不解析逻。
  • <1.1> 处,调用父类的 #getPropertySources() 方法,获得 PropertySource 数组。
  • <1.2> 处,调用 ConfigurationPropertySources#from(Iterable<PropertySource<?» sources) 方法,转换成 ConfigurationPropertySource 数组。上述两个变量的值,如下图所示: configurationPropertySources 属性
  • <2> 处,调用 Bindable#ofInstance(T instance) 方法,将 dubboConfig 包装成 Bindable 对象。
  • <3.1> 处,创建 Binder 对象。
  • <3.2> 处,调用 #getBindHandler() 方法,获得 BindHandler 对象。代码如下:
1
// RelaxedDubboConfigBinder.javaprivate BindHandler getBindHandler() {    // 获得默认的 BindHandler 处理器    BindHandler handler = BindHandler.DEFAULT;    // 进一步包装成 IgnoreErrorsBindHandler 对象    if (isIgnoreInvalidFields()) {        handler = new IgnoreErrorsBindHandler(handler);    }    // 进一步包装成 NoUnboundElementsBindHandler 对象    if (!isIgnoreUnknownFields()) {        UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();        handler = new NoUnboundElementsBindHandler(handler, filter);    }    return handler;}

```plain text

  • 关于 BindHandler 类,胖友不用深究,只要知道如下即可。 有时候,绑定时可能需要实现额外的逻辑,而BindHandler接口提供了一个很好的方法来实现这一点。 每个BindHandler都可以实现onStart,onSuccess,onFailure和onFinish方法来覆盖行为。 Spring Boot提供了一些处理程序,主要用于支持现有的@ConfigurationProperties绑定。 例如,ValidationBindHandler可用于对绑定对象应用Validator验证。

    ```

  • <3.3> 处,调用 Binder#bind(String name, Bindable target, BindHandler handler) 方法,将配置中,指定前缀( prefix )的属性,注入到 AbstractConfig 配置对象中。例如下图: dubboConfig 属性

如下开始,是 env 包下。

4.3 DubboDefaultPropertiesEnvironmentPostProcessor

com.alibaba.boot.dubbo.env.DubboDefaultPropertiesEnvironmentPostProcessor ,实现 EnvironmentPostProcessor、Ordered 接口,生成 Dubbo 默认的配置,添加到 environment 中。而需要生成的 Dubbo 默认的配置如下:

```plain text

直接复用 spring.application.name

dubbo.application.name=

默认为 true

dubbo.config.multiple=true

默认为 false

dubbo.config.qos-enable=false

1
2
3
4
5
6
7
8
9
10
11
12
13
- 因此,我们只要配置了
“spring.application.name”
的属性,
“dubbo.application.name”
就会自动生成。

### 4.3.1 postProcessEnvironment

实现 #postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) 方法,自动生成 Dubbo 默认配置。代码如下:

```java
// DubboDefaultPropertiesEnvironmentPostProcessor.java@Overridepublic void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {    MutablePropertySources propertySources = environment.getPropertySources();    // <1> 生成 Dubbo 默认配置    Map<String, Object> defaultProperties = createDefaultProperties(environment);    // <2> 有默认配置,则添加到 environment 中    if (!CollectionUtils.isEmpty(defaultProperties)) {        addOrReplace(propertySources, defaultProperties);    }}
  • <1> 处,调用 #createDefaultProperties(ConfigurableEnvironment environment) 方法,生成 Dubbo 默认配置。代码如下:
1
2
3
4
5
// DubboDefaultPropertiesEnvironmentPostProcessor.java/** * The property name of Spring Application
 * * @see ContextIdApplicationContextInitializer
 */private static final String SPRING_APPLICATION_NAME_PROPERTY = "spring.application.name";/** * The property name of {@link ApplicationConfig} * * @see EnableDubboConfig
 * @see EnableDubboConfigBinding
 */private static final String DUBBO_APPLICATION_NAME_PROPERTY = "dubbo.application.name";/** * The property name of {@link EnableDubboConfig#multiple() @EnableDubboConfig.multiple()} */private static final String DUBBO_CONFIG_MULTIPLE_PROPERTY = "dubbo.config.multiple";/** * The property name of {@link ApplicationConfig#getQosEnable() application's QOS enable} */private static final String DUBBO_APPLICATION_QOS_ENABLE_PROPERTY = "dubbo.application.qos-enable";private Map<String, Object> createDefaultProperties(ConfigurableEnvironment environment) {Map<String, Object> defaultProperties = new HashMap<String, Object>();// "dubbo.application.name"setDubboApplicationNameProperty(environment, defaultProperties);// "dubbo.config.multiple"setDubboConfigMultipleProperty(defaultProperties);// "dubbo.application.qos-enable"setDubboApplicationQosEnableProperty(defaultProperties);return defaultProperties;}private void setDubboApplicationNameProperty(Environment environment, Map<String, Object> defaultProperties) {    String springApplicationName = environment.getProperty(SPRING_APPLICATION_NAME_PROPERTY);    if (StringUtils.hasLength(springApplicationName)        && !environment.containsProperty(DUBBO_APPLICATION_NAME_PROPERTY)) {        defaultProperties.put(DUBBO_APPLICATION_NAME_PROPERTY, springApplicationName);    }}private void setDubboConfigMultipleProperty(Map<String, Object> defaultProperties) {    defaultProperties.put(DUBBO_CONFIG_MULTIPLE_PROPERTY, Boolean.TRUE.toString());}private void setDubboApplicationQosEnableProperty(Map<String, Object> defaultProperties) {    defaultProperties.put(DUBBO_APPLICATION_QOS_ENABLE_PROPERTY, Boolean.FALSE.toString());}

```plain text

  • 虽然比较长,但是比较简单。

    ```

  • <2> 处,有 Dubbo 默认配置,则添加到 environment 中。代码如下:
1
// DubboDefaultPropertiesEnvironmentPostProcessor.javaprivate static final String PROPERTY_SOURCE_NAME = "defaultProperties";private void addOrReplace(MutablePropertySources propertySources, Map<String, Object> map) {    // 情况一,获得到 "defaultProperties" 对应的 PropertySource 对象,则进行替换    MapPropertySource target = null;    if (propertySources.contains(PROPERTY_SOURCE_NAME)) {        PropertySource<?> source = propertySources.get(PROPERTY_SOURCE_NAME);        if (source instanceof MapPropertySource) { // 找到            target = (MapPropertySource) source;            // 遍历 map 数组,进行替换到 "defaultProperties" 中            for (String key : map.keySet()) {                if (!target.containsProperty(key)) {                    target.getSource().put(key, map.get(key));                }            }        }    }    // 情况二,不存在 "defaultProperties" 对应的 PropertySource 对象,则进行添加    if (target == null) {        target = new MapPropertySource(PROPERTY_SOURCE_NAME, map);    }    if (!propertySources.contains(PROPERTY_SOURCE_NAME)) {        propertySources.addLast(target);    }}

```plain text

  • 分成两种情况,也比较简单。胖友自己瞅瞅~

    ```

如下开始,是 context.event 包下。

4.4 WelcomeLogoApplicationListener

com.alibaba.boot.dubbo.context.event.WelcomeLogoApplicationListener ,实现 ApplicationListener 接口,处理 ApplicationEnvironmentPreparedEvent 事件,从而打印 Dubbo Banner 文本。代码如下:

1
2
3
4
// WelcomeLogoApplicationListener.java@Order(LoggingApplicationListener.DEFAULT_ORDER + 1)public class WelcomeLogoApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {    /**     * 是否执行过     *     * 通过该变量,保证有且仅处理一次 ApplicationEnvironmentPreparedEvent 事件     */    private static AtomicBoolean processed = new AtomicBoolean(false);    @Override    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {        // Skip if processed before, prevent duplicated execution in Hierarchical ApplicationContext        // 如果已经处理,则直接跳过        if (processed.get()) {            return;        }        // 获得 Logger 对象        /**         * Gets Logger After LoggingSystem configuration ready
         * @see LoggingApplicationListener
         */        final Logger logger = LoggerFactory.getLogger(getClass());        // 获得 Dubbo Banner 文本        String bannerText = buildBannerText();        if (logger.isInfoEnabled()) {            logger.info(bannerText);        } else {            System.out.print(bannerText);        }        // mark processed to be true        // 标记已执行        processed.compareAndSet(false, true);    }    String buildBannerText() {        StringBuilder bannerTextBuilder = new StringBuilder();        bannerTextBuilder
        .append(LINE_SEPARATOR)        .append(LINE_SEPARATOR)        .append(" :: Dubbo Spring Boot (v").append(Version.getVersion(getClass(), "1.0.0")).append(") : ")        .append(DUBBO_SPRING_BOOT_GITHUB_URL)        .append(LINE_SEPARATOR)        .append(" :: Dubbo (v").append(Version.getVersion()).append(") : ")        .append(DUBBO_GITHUB_URL)        .append(LINE_SEPARATOR)        .append(" :: Discuss group : ")        .append(DUBBO_MAILING_LIST)        .append(LINE_SEPARATOR);        return bannerTextBuilder.toString();    }}
  • 简单,就不多做解释了。

4.5 OverrideDubboConfigApplicationListener

com.alibaba.boot.dubbo.context.event.OverrideDubboConfigApplicationListener ,实现 ApplicationListener 接口,也是处理 ApplicationEnvironmentPreparedEvent 事件,根据 “dubbo.config.override” 的属性值,若为 true 时,则覆盖 environment 中 “dubbo.” 开头的配置,添加到 Dubbo Properties 对象中。代码如下:

1
2
3
// OverrideDubboConfigApplicationListener.java@Order // LOWEST_PRECEDENCE Make sure last executionpublic class OverrideDubboConfigApplicationListener implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {    @Override    public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) {        // 获得 Logger 对象        /**         * Gets Logger After LoggingSystem configuration ready
         * @see LoggingApplicationListener
         */        final Logger logger = LoggerFactory.getLogger(getClass());        // <1> 获得 "dubbo.config.override" 属性对应的值。默认情况下为 true        ConfigurableEnvironment environment = event.getEnvironment();        boolean override = environment.getProperty(OVERRIDE_CONFIG_PROPERTY_NAME, boolean.class, DEFAULT_OVERRIDE_CONFIG_PROPERTY_VALUE);        // <2> 如果要重写,则覆盖添加到 Dubbo Properties 中        if (override) {            // <2.1> 从 environment 中,提取 "dubbo." 开头的配置            SortedMap<String, Object> dubboProperties = filterDubboProperties(environment);            // <2.2> 添加到 Dubbo Properties 中            ConfigUtils.getProperties().putAll(dubboProperties);            if (logger.isInfoEnabled()) {                logger.info("Dubbo Config was overridden by externalized configuration {}", dubboProperties);            }        } else {            if (logger.isInfoEnabled()) {                logger.info("Disable override Dubbo Config caused by property {} = {}", OVERRIDE_CONFIG_PROPERTY_NAME, override);            }        }    }}
  • <1> 处,获得 “dubbo.config.override” 属性对应的值。默认情况下为 true 。代码如下:
1
2
3
// DubboUtils.java/** * The property name of override Dubbo config
 * <p> * The default value is {@link #DEFAULT_OVERRIDE_CONFIG_PROPERTY_VALUE} */public static final String OVERRIDE_CONFIG_PROPERTY_NAME = DUBBO_CONFIG_PREFIX + PROPERTY_NAME_SEPARATOR + "override";/** * The default property value of  override Dubbo config
 */public static final boolean DEFAULT_OVERRIDE_CONFIG_PROPERTY_VALUE = true;
  • <2> 处,如果要重写,则覆盖添加到 Dubbo Properties 中。
  • <2.1> 处,调用 DubboUtils#filterDubboProperties(ConfigurableEnvironment environment) 方法,从 environment 中,提取 “dubbo.” 开头的配置。代码如下:
1
2
3
4
// DubboUtils.java/** * The separator of property name
 */public static final String PROPERTY_NAME_SEPARATOR = ".";/** * The prefix of property name of Dubbo
 */public static final String DUBBO_PREFIX = "dubbo";/** * Filters Dubbo Properties from {@link ConfigurableEnvironment} * * @param environment {@link ConfigurableEnvironment} * @return Read-only SortedMap
 */public static SortedMap<String, Object> filterDubboProperties(ConfigurableEnvironment environment) {    SortedMap<String, Object> dubboProperties = new TreeMap<>();    // 获得所有的配置    Map<String, Object> properties = EnvironmentUtils.extractProperties(environment);    // 遍历配置,如果以 "dubbo." 开头,则添加到 dubboProperties 中    for (Map.Entry<String, Object> entry : properties.entrySet()) {        String propertyName = entry.getKey();        if (propertyName.startsWith(DUBBO_PREFIX + PROPERTY_NAME_SEPARATOR)            && entry.getValue() != null) {            dubboProperties.put(propertyName, entry.getValue().toString());        }    }    // 返回 dubboProperties    return Collections.unmodifiableSortedMap(dubboProperties);}

```plain text

  • 其中, EnvironmentUtils#extractProperties(ConfigurableEnvironment environment) 方法,获得所有的配置。考虑到篇幅就不赘述,艿艿已经添加注释,点击

    链接 查看。 ```

  • <2.2> 处,调用 Dubbo 的 ConfigUtils#getProperties() 方法,获得 Dubbo Properties 。然后再将 dubboProperties 变量,添加到 Dubbo Properties 中。

4.6 AwaitingNonWebApplicationListener

com.alibaba.boot.dubbo.context.event.AwaitingNonWebApplicationListener ,实现 SmartApplicationListener 接口,实现在非 Web 的环境下,提供 JVM 不退出关闭的功能,即 JVM 一直运行着。

胖友可以试试,启动一个非 Web 环境的 Spring Boot 应用,然后会发现,JVM 会在启动完 Spring Boot 应用后,自动关闭。

4.6.1 supportsEventType

实现 #supportsEventType(Class<? extends ApplicationEvent> eventType) 方法,判断支持的事件类型是 ApplicationReadyEvent 和 ContextClosedEvent 。代码如下:

1
// AwaitingNonWebApplicationListener.javaprivate static final Class<? extends ApplicationEvent>[] SUPPORTED_APPLICATION_EVENTS =of(ApplicationReadyEvent.class, ContextClosedEvent.class);@Overridepublic boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {    return ObjectUtils.containsElement(SUPPORTED_APPLICATION_EVENTS, eventType);}private static <T> T[] of(T... values) {    return values;}
  • 为什么呢,我们接着往下看。

4.6.2 supportsSourceType

实现 #supportsSourceType(Class<?> sourceType) 方法,判断支持的事件来源。代码如下:

1
// AwaitingNonWebApplicationListener.java@Overridepublic boolean supportsSourceType(Class<?> sourceType) {    return true;}
  • 全部返回 true ,意味支持所有的事件来源。

4.6.3 onApplicationEvent

1
// AwaitingNonWebApplicationListener.java@Overridepublic void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationReadyEvent) {        onApplicationReadyEvent((ApplicationReadyEvent) event); // <1>    } else if (event instanceof ContextClosedEvent) {        onContextClosedEvent((ContextClosedEvent) event); // <2>    }}
  • <1>「4.6.3.1 onApplicationReadyEvent」 处,当是 ApplicationReadyEvent 事件时,调用 #onApplicationReadyEvent(ApplicationReadyEvent event) 方法,处理 ApplicationReadyEvent 事件。详细解析,见 。
  • <2>「4.6.3.2 onContextClosedEvent」 处,当是 ContextClosedEvent 事件时,调用 #onApplicationReadyEvent(ContextClosedEvent event) 方法,处理 ApplicationReadyEvent 事件。详细解析,见 。

4.6.3.1 onApplicationReadyEvent

#onApplicationReadyEvent(ApplicationReadyEvent event) 方法,处理 ApplicationReadyEvent 事件。代码如下:

1
// AwaitingNonWebApplicationListener.javaprotected void onApplicationReadyEvent(ApplicationReadyEvent event) {    // <1> 如果是 Web 环境,则直接返回    final SpringApplication springApplication = event.getSpringApplication();    if (!WebApplicationType.NONE.equals(springApplication.getWebApplicationType())) {        return;    }    // <2> 启动一个用户线程,从而实现等待    await();}
  • <1> 处,如果是 Web 环境,则直接返回。因为,已经提供了 JVM 不退出关闭的功能。
  • <2> 处,调用 #await() 方法,启动一个用户线程,从而实现等待。代码如下:
1
// AwaitingNonWebApplicationListener.java/** * 是否已经等待完成 */private static final AtomicBoolean awaited = new AtomicBoolean(false);private final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();private final ExecutorService executorService = Executors.newSingleThreadExecutor();protected void await() {    // has been waited, return immediately    // 如果已经处于阻塞等待,直接返回    if (awaited.get()) {        return;    }    // 创建任务,实现阻塞    executorService.execute(() -> executeMutually(() -> {        while (!awaited.get()) {            if (logger.isInfoEnabled()) {                logger.info(" [Dubbo] Current Spring Boot Application is await...");            }            try {                condition.await();            } catch (InterruptedException e) {                Thread.currentThread().interrupt();            }        }    }));}private void executeMutually(Runnable runnable) {try {    lock.lock();    // <X> 执行 Runnable    runnable.run();} finally {    lock.unlock();}}

```plain text

  • 基于 Lock + Condition 实现等待通知。
  • #executeMutually(Runnable runnable) 方法,被 executorService 创建任务所调用。而该任务因为调用 Condition#await() 方法,阻塞等待。那么此时,JVM 至少有一个该用户线程未运行结束,那么此时

    JVM 关闭的条件 不被满足,所以就不会退出。 ```

4.6.3.2 onApplicationReadyEvent

#onApplicationReadyEvent(ContextClosedEvent event) 方法,处理 ContextClosedEvent 事件。代码如下:

1
// AwaitingNonWebApplicationListener.javaprotected void onContextClosedEvent(ContextClosedEvent event) {    // <1> 释放    release();    // <2> 关闭线程池    shutdown();}
  • <1> 处,调用 #release() 方法,进行释放。代码如下:
1
// AwaitingNonWebApplicationListener.javaprotected void release() {    executeMutually(() -> {        // CAS 设置 awaited 为 true        while (awaited.compareAndSet(false, true)) {            if (logger.isInfoEnabled()) {                logger.info(" [Dubbo] Current Spring Boot Application is about to shutdown...");            }            // 通知 Condition            condition.signalAll();        }    });}

```plain text

  • 通过调用 Condition#signalAll() 方法,通知 Condition 。从而在

    「4.6.3.1 onApplicationReadyEvent」 中,启动的线程的阻塞,进行停止。 ```

  • <2> 处,调用 #shutdown() 方法,关闭线程池。代码如下:
1
// AwaitingNonWebApplicationListener.javaprivate void shutdown() {    if (!executorService.isShutdown()) {        // Shutdown executorService        executorService.shutdown();    }}

5. dubbo-spring-boot-actuator 源码

dubbo-spring-boot-autoconfigure

模块,所有类如下图:

dubbo-spring-boot-autoconfigure 模块

5.1 使用指南

使用时,需要导入 dubbo-spring-boot-actuator 依赖。即如下:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 导入 WEB 环境 --><dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- 导入 Spring Boot Actuator --><dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- 导入 Dubbo Spring Boot Actuator --><dependency>
  <groupId>com.alibaba.boot</groupId>
  <artifactId>dubbo-spring-boot-actuator</artifactId>
</dependency>
观看本小节,胖友需要对 Spring Boot Actuator 有相关的了解。如果不知道,可以看看 [《一起来学 SpringBoot 2.x第十四篇:强大的 Actuator 服务监控与管理》](http://www.iocoder.cn/Spring-Boot/battcn/v2-actuator-introduce/) 文章。

另外,《Dubbo 官方文档 —— Dubbo Spring Boot Production-Ready》 文章,也是需要先瞅瞅的。

如果胖友使用的是 Spring Boot 2,有一个坑要注意,因为 《Spring Boot 2.0 的 Actuator 只暴露 health 和 info》 ,所以需要手动在配置文件中,添加要开启的 Dubbo Endpoint 。例如:

```plain text

application.properties

management.endpoints.web.exposure.include=health,info,dubbo,dubboconfigs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
- 此时,我们多开启了
dubbo
和
dubboconfigs
这两个 EndPoint 。T T 坑了自己好久~

如下开始,是 autoconfigure 包下。

## 5.2 DubboEndpointsAutoConfiguration

com.alibaba.boot.dubbo.actuate.autoconfigure.DubboEndpointsAutoConfiguration ,Dubbo Endpoint 自动配置类。代码如下:

```java
// DubboEndpointsAutoConfiguration.java@Configuration@PropertySource(    name = "Dubbo Endpoints Default Properties",    value = "classpath:/META-INF/dubbo-endpoins-default.properties") // 导入该配置文件public class DubboEndpointsAutoConfiguration {    @Bean    @ConditionalOnMissingBean    @ConditionalOnEnabledEndpoint    public DubboEndpoint dubboEndpoint() {        return new DubboEndpoint();    }    @Bean    @ConditionalOnMissingBean    @ConditionalOnEnabledEndpoint    public DubboConfigsMetadataEndpoint dubboConfigsMetadataEndpoint() {        return new DubboConfigsMetadataEndpoint();    }    @Bean    @ConditionalOnMissingBean    @ConditionalOnEnabledEndpoint    public DubboPropertiesEndpoint dubboPropertiesEndpoint() {        return new DubboPropertiesEndpoint();    }    @Bean    @ConditionalOnMissingBean    @ConditionalOnEnabledEndpoint    public DubboReferencesMetadataEndpoint dubboReferencesMetadataEndpoint() {        return new DubboReferencesMetadataEndpoint();    }    @Bean    @ConditionalOnMissingBean    @ConditionalOnEnabledEndpoint    public DubboServicesMetadataEndpoint dubboServicesMetadataEndpoint() {        return new DubboServicesMetadataEndpoint();    }    @Bean    @ConditionalOnMissingBean    @ConditionalOnEnabledEndpoint    public DubboShutdownEndpoint dubboShutdownEndpoint() {        return new DubboShutdownEndpoint();    }}
  • 每个方法,创建一个 Dubbo Endpint Bean 。一共有 6 个。
  • @PropertySource 注解,导入 “classpath:/META-INF/dubbo-endpoins-default.properties” 配置文件。代码如下:

```plain text

dubbo-endpoins-default.properties

Dubbo Endpoints Default Properties is loaded by @PropertySource with low order,

those values of properties can be override by higher PropertySource

@see DubboEndpointsAutoConfiguration

Set enabled for Dubbo Endpoints 设置 Dubbo Endpoints 是否开启

management.endpoint.dubbo.enabled = true management.endpoint.dubboshutdown.enabled = false management.endpoint.dubboconfigs.enabled = true management.endpoint.dubboservices.enabled = false management.endpoint.dubboreferences.enabled = false management.endpoint.dubboproperties.enabled = true

“management.endpoints.web.base-path” should not be configured in this file

Re-defines path-mapping of Dubbo Web Endpoints 重命名 Dubbo Web Endpoints 路径

management.endpoints.web.path-mapping.dubboshutdown = dubbo/shutdown management.endpoints.web.path-mapping.dubboconfigs = dubbo/configs management.endpoints.web.path-mapping.dubboservices = dubbo/services management.endpoints.web.path-mapping.dubboreferences = dubbo/references management.endpoints.web.path-mapping.dubboproperties = dubbo/properties

1
2
3
4
5
6
7
## 5.3 DubboHealthIndicatorAutoConfiguration

com.alibaba.boot.dubbo.actuate.autoconfigure.DubboHealthIndicatorAutoConfiguration ,Dubbo Health Indicator 自动配置类。代码如下:

```java
// DubboHealthIndicatorAutoConfiguration.java@Configuration@ConditionalOnClass({HealthIndicator.class}) // 存在 HealthIndicator 类的情况@AutoConfigureBefore({EndpointAutoConfiguration.class}) // 在 EndpointAutoConfiguration 自动配置类之前初始化@AutoConfigureAfter(DubboAutoConfiguration.class) // 在 DubboAutoConfiguration 自动配置类之后初始化@ConditionalOnEnabledHealthIndicator("dubbo")@EnableConfigurationProperties(DubboHealthIndicatorProperties.class) // 自动配置 DubboHealthIndicatorProperties 类public class DubboHealthIndicatorAutoConfiguration {    @Bean    @ConditionalOnMissingBean    public DubboHealthIndicator dubboHealthIndicator() {        return new DubboHealthIndicator();    }}
  • 每个注解,看后面的代码注释。
  • 唯一的方法,创建 DubboHealthIndicator Bean 对象。详细解析,见 「5.5 DubboHealthIndicator」

5.4 DubboHealthIndicatorProperties

com.alibaba.boot.dubbo.actuate.health.DubboHealthIndicatorProperties ,Dubbo Health Indicator Properties 类。代码代码如下:

1
2
3
4
5
6
7
8
9
10
// DubboHealthIndicatorProperties.java@ConfigurationProperties(prefix = PREFIX, ignoreUnknownFields = false) // "management.health.dubbo" 开头的配置public class DubboHealthIndicatorProperties {    /**     * The prefix of {@link DubboHealthIndicatorProperties}     */    public static final String PREFIX = "management.health.dubbo";    private Status status = new Status();    // ... 省略 setting/getting 方法    /**     * The nested class for {@link StatusChecker}'s names
     * <pre>     * registry=com.alibaba.dubbo.registry.status.RegistryStatusChecker
     * spring=com.alibaba.dubbo.config.spring.status.SpringStatusChecker
     * datasource=com.alibaba.dubbo.config.spring.status.DataSourceStatusChecker
     * memory=com.alibaba.dubbo.common.status.support.MemoryStatusChecker
     * load=com.alibaba.dubbo.common.status.support.LoadStatusChecker
     * server=com.alibaba.dubbo.rpc.protocol.dubbo.status.ServerStatusChecker
     * threadpool=com.alibaba.dubbo.rpc.protocol.dubbo.status.ThreadPoolStatusChecker
     * </pre>     *     * @see StatusChecker
     */    public static class Status {        /**         * The defaults names of {@link StatusChecker}         * <p>         * The defaults : "memory", "load"         */        private Set<String> defaults = new LinkedHashSet<>(Arrays.asList("memory", "load"));        /**         * The extra names of {@link StatusChecker}         *         * 配置的 "management.health.dubbo.extras" 集合         *         * 每个元素,是 StatusChecker 的实现类         */        private Set<String> extras = new LinkedHashSet<>();        // ... 省略 setting/getting 方法    }}
  • 读取以 “management.health.dubbo” 开头的配置。

如下开始,是 health 包下。

5.5 DubboHealthIndicator

com.alibaba.boot.dubbo.actuate.health.DubboHealthIndicator ,继承 AbstractHealthIndicator 抽象类,Dubbo Health Indicator 实现类。代码如下:

5.4.1 doHealthCheck

实现 #doHealthCheck(Health.Builder builder) 方法,执行健康检查。代码如下:

在请求 /actuator/health 接口时,也会调用该方法。

1
// DubboHealthIndicator.java@Overrideprotected void doHealthCheck(Health.Builder builder) throws Exception {    // <1> 获得 StatusChecker 对应的 Dubbo ExtensionLoader 对象    ExtensionLoader<StatusChecker> extensionLoader = getExtensionLoader(StatusChecker.class);    // <2> 解析 StatusChecker 的名字的 Map    Map<String, String> statusCheckerNamesMap = resolveStatusCheckerNamesMap();    // <3> 声明 hasError、hasUnknown 变量    boolean hasError = false; // 是否有错误的返回    boolean hasUnknown = false; // 是否有未知的返回    // Up first    // <4> 先 builder 标记状态是 UP    builder.up();    // <5> 遍历 statusCheckerNamesMap 元素    for (Map.Entry<String, String> entry : statusCheckerNamesMap.entrySet()) {        // <6.1> 获得 StatusChecker 的名字        String statusCheckerName = entry.getKey();        // <6.2> 获得 source        String source = entry.getValue();        // <6.3> 获得 StatusChecker 对象        StatusChecker checker = extensionLoader.getExtension(statusCheckerName);        // <6.4> 执行校验        com.alibaba.dubbo.common.status.Status status = checker.check();        // <7.1> 获得校验结果        com.alibaba.dubbo.common.status.Status.Level level = status.getLevel();        // <7.2> 如果是 ERROR 检验结果,则标记 hasError 为 true ,并标记 builder 状态为 down        if (!hasError // 不存在 hasError 的时候            && level.equals(com.alibaba.dubbo.common.status.Status.Level.ERROR)) {            hasError = true;            builder.down();        }        // <7.3> 如果是 UNKNOWN 检验结果,则标记 hasUnknown 为 true ,并标记 builder 状态为 unknown        if (!hasError && !hasUnknown // 不存在 hasError 且不存在 hasUnknown            && level.equals(com.alibaba.dubbo.common.status.Status.Level.UNKNOWN)) {            hasUnknown = true;            builder.unknown();        }        // <8.1> 创建 detail Map        Map<String, Object> detail = new LinkedHashMap<>();        // <8.2> 设置 detail 属性值        detail.put("source", source);        detail.put("status", status); // 校验结果        // <8.3> 添加到 builder 中        builder.withDetail(statusCheckerName, detail);    }}
  • 大体比较简单,胖友顺着注释来瞅瞅即可。
  • <2>「5.4.2 resolveStatusCheckerNamesMap」 处,调用 #resolveStatusCheckerNamesMap() 方法,解析 StatusChecker 的名字的 Map 。因为这个对后续逻辑非常关键,所以胖友先跳到 中。看完之后,在回到此处。
  • 最终返回 builder 的结果,如下图: builder 结果

5.4.2 resolveStatusCheckerNamesMap

#resolveStatusCheckerNamesMap() 方法,解析 StatusChecker 的名字的 Map。代码如下:

1
2
// DubboHealthIndicator.java/** * Resolves the map of {@link StatusChecker}'s name and its' source. * * 解析 StatusChecker 的名字的 Map
 * * KEYStatusChecker 的名字 * VALUE配置的来源 * * @return non-null {@link Map} */protected Map<String, String> resolveStatusCheckerNamesMap() {    // 创建 Map    Map<String, String> statusCheckerNamesMap = new LinkedHashMap<>();    // <1> 从 DubboHealthIndicatorProperties 中获取    statusCheckerNamesMap.putAll(resolveStatusCheckerNamesMapFromDubboHealthIndicatorProperties());    // <2> 从 ProtocolConfig 中获取    statusCheckerNamesMap.putAll(resolveStatusCheckerNamesMapFromProtocolConfigs());    // <3> 从 ProviderConfig 中获取    statusCheckerNamesMap.putAll(resolveStatusCheckerNamesMapFromProviderConfig());    return statusCheckerNamesMap;}
  • <1> 处,调用 #resolveStatusCheckerNamesMapFromDubboHealthIndicatorProperties() 方法,从 DubboHealthIndicatorProperties 中获取。代码如下:
1
// DubboHealthIndicator.java@Autowiredprivate DubboHealthIndicatorProperties dubboHealthIndicatorProperties;private Map<String, String> resolveStatusCheckerNamesMapFromDubboHealthIndicatorProperties() {    // 获得 DubboHealthIndicatorProperties.Status    DubboHealthIndicatorProperties.Status status = dubboHealthIndicatorProperties.getStatus();    // 创建 Map    Map<String, String> statusCheckerNamesMap = new LinkedHashMap<>();    // 1. 读取 defaults 属性    for (String statusName : status.getDefaults()) {        statusCheckerNamesMap.put(statusName, PREFIX + ".status.defaults");    }    // 2. 读取 extras 属性    for (String statusName : status.getExtras()) {        statusCheckerNamesMap.put(statusName, PREFIX + ".status.extras");    }    return statusCheckerNamesMap;}
  • <2> 处,调用 #resolveStatusCheckerNamesMapFromProtocolConfigs() 方法,从 ProtocolConfig 中获取。代码如下:
1
// DubboHealthIndicator.java@Autowired(required = false)private Map<String, ProtocolConfig> protocolConfigs = Collections.emptyMap();private Map<String, String> resolveStatusCheckerNamesMapFromProtocolConfigs() {    // 创建 Map    Map<String, String> statusCheckerNamesMap = new LinkedHashMap<>();    // 遍历 protocolConfigs    for (Map.Entry<String, ProtocolConfig> entry : protocolConfigs.entrySet()) {        // 获得 Bean 的名字        String beanName = entry.getKey();        // 获得 ProtocolConfig 对象        ProtocolConfig protocolConfig = entry.getValue();        // 获得 ProtocolConfig 的 StatusChecker 的名字的集合        Set<String> statusCheckerNames = getStatusCheckerNames(protocolConfig);        // 遍历 statusCheckerNames 数组        for (String statusCheckerName : statusCheckerNames) {            // 构建 source 属性            String source = buildSource(beanName, protocolConfig);            // 添加到 statusCheckerNamesMap 中            statusCheckerNamesMap.put(statusCheckerName, source);        }    }    return statusCheckerNamesMap;}private Set<String> getStatusCheckerNames(ProtocolConfig protocolConfig) {String status = protocolConfig.getStatus();return StringUtils.commaDelimitedListToSet(status);}private Set<String> getStatusCheckerNames(ProviderConfig providerConfig) {String status = providerConfig.getStatus();return StringUtils.commaDelimitedListToSet(status);}private String buildSource(String beanName, Object bean) {    return beanName + "@" + bean.getClass().getSimpleName() + ".getStatus()";}
  • <3> 处,调用 #resolveStatusCheckerNamesMapFromProviderConfig() 方法,从 ProviderConfig 中获取。代码如下:
1
// DubboHealthIndicator.java@Autowired(required = false)private Map<String, ProviderConfig> providerConfigs = Collections.emptyMap();private Map<String, String> resolveStatusCheckerNamesMapFromProviderConfig() {    // 创建 Map    Map<String, String> statusCheckerNamesMap = new LinkedHashMap<>();    // 遍历 providerConfigs    for (Map.Entry<String, ProviderConfig> entry : providerConfigs.entrySet()) {        // 获得 Bean 的名字        String beanName = entry.getKey();        // 获得 ProviderConfig 对象        ProviderConfig providerConfig = entry.getValue();        // 获得 ProtocolConfig 的 StatusChecker 的名字的集合        Set<String> statusCheckerNames = getStatusCheckerNames(providerConfig);        // 遍历 statusCheckerNames 数组        for (String statusCheckerName : statusCheckerNames) {            // 构建 source 属性            String source = buildSource(beanName, providerConfig);            // 添加到 statusCheckerNamesMap 中            statusCheckerNamesMap.put(statusCheckerName, source);        }    }    return statusCheckerNamesMap;}
  • 最终的结果,示例如下图: statusCheckerNamesMap 属性

如下开始,是 endpoint 包下。

5.6 AbstractDubboEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.AbstractDubboEndpoint ,实现 ApplicationContextAware、EnvironmentAware 接口,Dubbo Endpoint 抽象类,提供给子类工具方法。

5.6.1 基本属性

1
// AbstractDubboEndpoint.javaprotected ApplicationContext applicationContext;protected ConfigurableEnvironment environment;@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {    this.applicationContext = applicationContext;}@Overridepublic void setEnvironment(Environment environment) {if (environment instanceof ConfigurableEnvironment) {    this.environment = (ConfigurableEnvironment) environment;}}

5.6.2 resolveBeanMetadata

#resolveBeanMetadata(Object bean) 方法,获得 Bean 的元数据。代码如下:

1
// AbstractDubboEndpoint.javaprotected Map<String, Object> resolveBeanMetadata(final Object bean) {    // 创建 Map    final Map<String, Object> beanMetadata = new LinkedHashMap<>();    try {        // 获得 BeanInfo 对象        BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());        // 获得 PropertyDescriptor 数组        PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();        // 遍历 PropertyDescriptor 数组        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {            // 获得 Method 对象            Method readMethod = propertyDescriptor.getReadMethod();            // 读取属性,添加到 beanMetadata 中            if (readMethod != null && isSimpleType(propertyDescriptor.getPropertyType())) {                String name = Introspector.decapitalize(propertyDescriptor.getName());                Object value = readMethod.invoke(bean);                beanMetadata.put(name, value);            }        }    } catch (Exception e) {        throw new RuntimeException(e);    }    return beanMetadata;}private static boolean isSimpleType(Class<?> type) {    return isPrimitiveOrWrapper(type) // 基本类型 or 包装类型    || type == String.class    || type == BigDecimal.class    || type == BigInteger.class    || type == Date.class    || type == URL.class    || type == Class.class    ;}
  • 目的是读取 Bean 的基本属性。示例如下图: beanMetadata 结果

5.6.3 getServiceBeansMap

#getServiceBeansMap() 方法,获得所有 ServiceBean 。代码如下:

1
// AbstractDubboEndpoint.javaprotected Map<String, ServiceBean> getServiceBeansMap() {    return BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ServiceBean.class);}

5.6.4 getProtocolConfigsBeanMap

#getProtocolConfigsBeanMap() 方法,获得所有 ProtocolConfig 。代码如下:

1
// AbstractDubboEndpoint.javaprotected Map<String, ProtocolConfig> getProtocolConfigsBeanMap() {    return BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class);}

5.6.5 getReferenceAnnotationBeanPostProcessor

#getReferenceAnnotationBeanPostProcessor() 方法,获得 ReferenceAnnotationBeanPostProcessor Bean 对象。代码如下:

1
// AbstractDubboEndpoint.javaprotected ReferenceAnnotationBeanPostProcessor getReferenceAnnotationBeanPostProcessor() {    return applicationContext.getBean(ReferenceAnnotationBeanPostProcessor.BEAN_NAME, ReferenceAnnotationBeanPostProcessor.class);}

5.7 DubboEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.DubboEndpoint ,Dubbo Endpoint ,获得 Dubbo Meta Data(元数据)。代码如下:

1
2
// DubboEndpoint.java/** * Actuator {@link Endpoint} to expose Dubbo Meta Data
 */@Endpoint(id = "dubbo")public class DubboEndpoint {    @ReadOperation    public Map<String, Object> invoke() {        // 创建 Map        Map<String, Object> metaData = new LinkedHashMap<>();        // timestamp        metaData.put("timestamp", System.currentTimeMillis());        // versions        Map<String, String> versions = new LinkedHashMap<>();        versions.put("dubbo-spring-boot", Version.getVersion(DubboUtils.class, "1.0.0"));        versions.put("dubbo", Version.getVersion());        // urls        Map<String, String> urls = new LinkedHashMap<>();        urls.put("dubbo", DUBBO_GITHUB_URL);        urls.put("mailing-list", DUBBO_MAILING_LIST);        urls.put("github", DUBBO_SPRING_BOOT_GITHUB_URL);        urls.put("issues", DUBBO_SPRING_BOOT_ISSUES_URL);        urls.put("git", DUBBO_SPRING_BOOT_GIT_URL);        metaData.put("versions", versions);        metaData.put("urls", urls);        return metaData;    }}

5.8 DubboConfigsMetadataEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.DubboConfigsMetadataEndpoint ,继承 AbstractDubboEndpoint 抽象类,获得 所有的 Dubbo 配置类的元数据。代码如下:

1
// DubboConfigsMetadataEndpoint.java/** * Dubbo Configs Metadata {@link Endpoint} */@Endpoint(id = "dubboconfigs")public class DubboConfigsMetadataEndpoint extends AbstractDubboEndpoint {    @ReadOperation    public Map<String, Map<String, Map<String, Object>>> configs() {        // 创建 Map        // KEY:获得类的简称。例如:ApplicationConfig、ConsumerConfig        // KEY2:Bean 的名称        // VALUE:Bean 的元数据        Map<String, Map<String, Map<String, Object>>> configsMap = new LinkedHashMap<>();        // 遍历每个配置类,添加其的 Bean 们,到 configsMap 中        addDubboConfigBeans(ApplicationConfig.class, configsMap);        addDubboConfigBeans(ConsumerConfig.class, configsMap);        addDubboConfigBeans(MethodConfig.class, configsMap);        addDubboConfigBeans(ModuleConfig.class, configsMap);        addDubboConfigBeans(MonitorConfig.class, configsMap);        addDubboConfigBeans(ProtocolConfig.class, configsMap);        addDubboConfigBeans(ProviderConfig.class, configsMap);        addDubboConfigBeans(ReferenceConfig.class, configsMap);        addDubboConfigBeans(RegistryConfig.class, configsMap);        addDubboConfigBeans(ServiceConfig.class, configsMap);        return configsMap;    }    private void addDubboConfigBeans(Class<? extends AbstractConfig> dubboConfigClass, Map<String, Map<String, Map<String, Object>>> configsMap) {        // 获得指定类 dubboConfigClass 的 Map        Map<String, ? extends AbstractConfig> dubboConfigBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, dubboConfigClass);        // 获得类的简称。例如:ApplicationConfig、ConsumerConfig        String name = dubboConfigClass.getSimpleName();        // 创建 Map        Map<String, Map<String, Object>> beansMetadata = new TreeMap<>();        // 遍历 dubboConfigBeans 数组        for (Map.Entry<String, ? extends AbstractConfig> entry : dubboConfigBeans.entrySet()) {            // 获得 Bean 的名字            String beanName = entry.getKey();            // 获得 Bean 的元数据            AbstractConfig configBean = entry.getValue();            Map<String, Object> configBeanMeta = super.resolveBeanMetadata(configBean);            // 添加到 beansMetadata 中            beansMetadata.put(beanName, configBeanMeta);        }        // 添加到 configsMap 中        configsMap.put(name, beansMetadata);    }}

5.7 DubboPropertiesEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.DubboPropertiesEndpoint ,继承 AbstractDubboEndpoint 抽象类,获得 Dubbo Properties 。代码如下:

1
// DubboPropertiesEndpoint.java/** * Dubbo Properties {@link Endpoint} */@Endpoint(id = "dubboproperties")public class DubboPropertiesEndpoint extends AbstractDubboEndpoint {    @ReadOperation    public SortedMap<String, Object> properties() {        return DubboUtils.filterDubboProperties(environment);    }}

5.8 DubboReferencesMetadataEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.DubboReferencesMetadataEndpoint ,继承 AbstractDubboEndpoint 抽象类,获得所有的 Dubbo @Reference Bean 的元数据。代码如下:

1
// DubboReferencesMetadataEndpoint.java/** * Dubbo {@link Reference} Metadata {@link Endpoint} */@Endpoint(id = "dubboreferences")public class DubboReferencesMetadataEndpoint extends AbstractDubboEndpoint {    @ReadOperation    public Map<String, Map<String, Object>> references() {        // 创建 Map        // KEY:Bean 的名字        // VALUE:Bean 的元数据        Map<String, Map<String, Object>> referencesMetadata = new LinkedHashMap<>();        // 获得 ReferenceAnnotationBeanPostProcessor Bean 对象        ReferenceAnnotationBeanPostProcessor beanPostProcessor = super.getReferenceAnnotationBeanPostProcessor();        // injected Field ReferenceBean Cache        referencesMetadata.putAll(buildReferencesMetadata(beanPostProcessor.getInjectedFieldReferenceBeanMap()));        // injected Method ReferenceBean Cache        referencesMetadata.putAll(buildReferencesMetadata(beanPostProcessor.getInjectedMethodReferenceBeanMap()));        return referencesMetadata;    }    private Map<String, Map<String, Object>> buildReferencesMetadata(Map<InjectionMetadata.InjectedElement, ReferenceBean<?>> injectedElementReferenceBeanMap) {        // 创建 Map        // KEY:Bean 的名字        // VALUE:Bean 的元数据        Map<String, Map<String, Object>> referencesMetadata = new LinkedHashMap<>();        // 遍历 injectedElementReferenceBeanMap 元素        for (Map.Entry<InjectionMetadata.InjectedElement, ReferenceBean<?>> entry : injectedElementReferenceBeanMap.entrySet()) {            InjectionMetadata.InjectedElement injectedElement = entry.getKey();            // 获得 ReferenceBean 对象            ReferenceBean<?> referenceBean = entry.getValue();            // 获得 Bean 元数据            Map<String, Object> beanMetadata = super.resolveBeanMetadata(referenceBean);            // 获得 invoker 属性            beanMetadata.put("invoker", super.resolveBeanMetadata(referenceBean.get()));            // 添加到 referencesMetadata 中            referencesMetadata.put(String.valueOf(injectedElement.getMember()), beanMetadata);        }        return referencesMetadata;    }}

5.9 DubboServicesMetadataEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.DubboServicesMetadataEndpoint ,继承 AbstractDubboEndpoint 抽象类,获得所有的 Dubbo @Service Bean 的元数据。代码如下:

1
// DubboServicesMetadataEndpoint.java/** * Dubbo {@link Service} Metadata {@link Endpoint} */@Endpoint(id = "dubboservices")public class DubboServicesMetadataEndpoint extends AbstractDubboEndpoint {    @ReadOperation    public Map<String, Map<String, Object>> services() {        // 获得所有的 ServiceBean        Map<String, ServiceBean> serviceBeansMap = super.getServiceBeansMap();        // 创建 Map        // KEY:Bean 的名字        // VALUE:Bean 的元数据        Map<String, Map<String, Object>> servicesMetadata = new LinkedHashMap<>(serviceBeansMap.size());        // 遍历 serviceBeansMap 元素        for (Map.Entry<String, ServiceBean> entry : serviceBeansMap.entrySet()) {            // 获得 Bean 的名字            String serviceBeanName = entry.getKey();            // 获得 ServiceBean 对象            ServiceBean serviceBean = entry.getValue();            // 获得 Bean 的元数据            Map<String, Object> serviceBeanMetadata = super.resolveBeanMetadata(serviceBean);            // 获得 Service 对象。若获得到,则添加到 serviceBeanMetadata 中            Object service = resolveServiceBean(serviceBeanName, serviceBean);            if (service != null) {                // Add Service implementation class                serviceBeanMetadata.put("serviceClass", service.getClass().getName());            }            // 添加到 servicesMetadata 中            servicesMetadata.put(serviceBeanName, serviceBeanMetadata);        }        return servicesMetadata;    }    private Object resolveServiceBean(String serviceBeanName, ServiceBean serviceBean) {        int index = serviceBeanName.indexOf("#");        if (index > -1) {            Class<?> interfaceClass = serviceBean.getInterfaceClass();            String serviceName = serviceBeanName.substring(index + 1);            if (applicationContext.containsBean(serviceName)) {                return applicationContext.getBean(serviceName, interfaceClass);            }        }        return null;    }}

5.10 DubboShutdownEndpoint

com.alibaba.boot.dubbo.actuate.endpoint.DubboShutdownEndpoint ,继承 AbstractDubboEndpoint 抽象类,关闭 Dubbo 。代码如下:

1
2
// DubboShutdownEndpoint.java/** * Dubbo Shutdown
 */@Endpoint(id = "dubboshutdown")public class DubboShutdownEndpoint extends AbstractDubboEndpoint {    @WriteOperation    public Map<String, Object> shutdown() throws Exception {        // 创建 Map        Map<String, Object> shutdownCountData = new LinkedHashMap<>();        // registries        // 获得注册的数量        int registriesCount = AbstractRegistryFactory.getRegistries().size();        // protocols        // 获得 Protocol 的数量        int protocolsCount = super.getProtocolConfigsBeanMap().size();        // 销毁 ProtocolConfig        ProtocolConfig.destroyAll();        // 添加到 shutdownCountData 中        shutdownCountData.put("registries", registriesCount);        shutdownCountData.put("protocols", protocolsCount);        // Service Beans        // 获得所有 ServiceBean ,然后逐个销毁        Map<String, ServiceBean> serviceBeansMap = super.getServiceBeansMap();        if (!serviceBeansMap.isEmpty()) {            for (ServiceBean serviceBean : serviceBeansMap.values()) {                serviceBean.destroy();            }        }        // 添加到 shutdownCountData 中        shutdownCountData.put("services", serviceBeansMap.size());        // Reference Beans        // 获得 ReferenceAnnotationBeanPostProcessor 对象        ReferenceAnnotationBeanPostProcessor beanPostProcessor = super.getReferenceAnnotationBeanPostProcessor();        // 获得 Reference Bean 的数量        int referencesCount = beanPostProcessor.getReferenceBeans().size();        // 销毁所有 Reference Bean        beanPostProcessor.destroy();        // 添加到 shutdownCountData 中        shutdownCountData.put("references", referencesCount);        // Set Result to complete        Map<String, Object> shutdownData = new TreeMap<>();        shutdownData.put("shutdown.count", shutdownCountData);        return shutdownData;    }}
  • 通过调用该接口,我们就可以远程关闭 Dubbo 服务。

666. 彩蛋

总的来说,比较简单。实际上,比较重的逻辑,在 Dubbo 本身上。所以呢,感兴趣的胖友,可以自己去撸一撸。嘻嘻嘻

本文由作者按照 CC BY 4.0 进行授权