乐趣区

关于springboot:SpringBoot成长记4Run方法中配置文件的处理

上一节,咱们次要理解了 SpringBoot 的一个扩大点设计 SpringApplicationRunListeners。并没有找到咱们想要找到的 Spring 容器创立和 web 容器启动、主动拆卸配置的这些外围性能。

之前咱们说过,xxxxEnvironment 示意了配置文件的封装,这一节就让咱们来看下,SpringBoot 启动过程中,如何通过解决配置文件,设置到 Environment 对象中的。

咱们接着往下持续剖析 run 办法,会看到如下代码:

public ConfigurableApplicationContext run(String... args) {//1、扩大点 SpringApplicationRunListeners listeners.starting();
    //2、配置文件的解决(待剖析)ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     ConfigurableEnvironment environment = prepareEnvironment(listeners,
      applicationArguments);
     configureIgnoreBeanInfo(environment);
     Banner printedBanner = printBanner(environment);
     // 其余逻辑
}

咱们还是抓大放小,外围关注这一段逻辑,显著是重复呈现了 environment 这个词汇相干的代码,必定就是这里解决的配置文件。

这段代码次要的逻辑能够概括如下:

1)DefaultApplicationArguments 命令行参数的解析和封装,也不是咱们关注的重点

2)prepareEnvironment 这个办法应该就是真正配置文件的解决逻辑

3)前面这两个办法,configureIgnoreBeanInfo、printBanner,明细就是基于配置设置一个参数、打印一下 Banner 日志输入而已,一看就不是重点。

即如下图所示:

通过下面的初步剖析,咱们能够看到,外围关注的应该是 prepareEnvironment 这个办法的逻辑,那接下来就让咱们看看它是如何解决的配置文件吧。

SpringBoot 的配置文件解析如何设计的?

在剖析 SpringBoot prepareEnvironment 办法是如何解决配置文件之前,你能够思考下,如果让你编写配置文件的解析,你会怎么思考呢?你可能会思考:

从哪里找到配置文件,之后依据文件格式解析下配置文件,那每个配置文件我都能够形象为一个对象,对象中能够有配置文件的名称,地位,具体配置值等等。最初配置文件可能是多个,须要一个汇合来寄存这些对象。形成一个列表,示意多个配置文件解析后的后果。

这个思路其实你略微思考下,就能够得出。

那么 SpringBoot 其实也没有什么浅近的,它大体也是这个思路,你有这个思路,再去了解代码,其实就会轻松很多。这个思维是我想要教给你们的,对了解代码会十分有用。

让咱们来一起看下代码:

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

这个办法的脉络,大体就是:

1)创立了一个 ConfigurableEnvironment

2)之后一直往里面设置或者放了一些值,很可能就是各种配置文件的解析后的后果。configureEnvironment、attach 之类的。

3)两头还执行了要害的 listeners 的扩大点的一个办法—environmentPrepared()

整体如下图所示:

下面的逻辑看着还是比拟多的,然而 外围就是创立了 ConfigurableEnvironment,之后给它设置了一堆属性而已。

创立的 ConfigurableEnvironment 配置对象形象了哪些货色?

ConfigurableEnvironment 是通过一个办法 getOrCreateEnvironment()创立得来的。

你能够关上接口看下它的脉络,而 ConfigurableEnvironment 这个类时一个接口,定义了一些和配置相干办法。

profile 示意多环境配置看,MutablePropertySources 是个很要害的对象,外部蕴含了一个 List<PropertySource> 对象, 这个你能够猜想到它就是对配置文件的形象对象,每一个 PropertySource 示意一个配置文件。其余的就是一些 get 办法了,整体类图如下所示:

理解了这个接口,你大体应该有个印象了,配置文件形象的关键点,就是 PropertySource+profile 封装到 ConfigurableEnvironment 这个接口实现类中。

这个其实跟咱们之前提到过的,让你本人设计配置文件的解析的思维很相似的。你能够思考下这里的 亮点:

1)PropertySource name 能够用作配置文件名称,T source 属性是泛型,能够放常见的 key-value 的 properties 和 yml 的配置文件解析后果,此时 T 就是一个 Map,也能够放其余配置格局解析成的对象,这里没有限度的。这点设计十分好。

2)通过 List<PropertySource> 对立治理多个配置文件,并且通过 MutablePropertySources 封装对这个汇合的常见操作。

3)至于 profile 的设计,就是反对多环境配置,只有通过一个 Set<String> profile 汇合就能够实现,这个思路其实很简略。

创立时候默认增加了那些配置文件的解析?

当你晓得了下面的设计思维,那么了解代码就不艰难了。首先是创立 Environment 的代码:

private ConfigurableEnvironment getOrCreateEnvironment() {if (this.environment != null) {return this.environment;}
   switch (this.webApplicationType) {
   case SERVLET:
      return new StandardServletEnvironment();
   case REACTIVE:
      return new StandardReactiveWebEnvironment();
   default:
      return new StandardEnvironment();}
}

依据 webApplicationType 利用类型,之前创立 SpringApplication 时推断过,默认是 SERVLET。所以这里创立了一个实现类是StandardServletEnvironment。

而这个实现类,默认加载了几个配置。尽管默认构造方法为空,然而父类的构造方法调用了 customizePropertySources,理论还是初始化了一些配置文件。代码如下:

public class StandardServletEnvironment extends StandardEnvironment implements ConfigurableWebEnvironment {
    public static final String SERVLET_CONTEXT_PROPERTY_SOURCE_NAME = "servletContextInitParams";
    public static final String SERVLET_CONFIG_PROPERTY_SOURCE_NAME = "servletConfigInitParams";
    public static final String JNDI_PROPERTY_SOURCE_NAME = "jndiProperties";

    public StandardServletEnvironment() {}

    protected void customizePropertySources(MutablePropertySources propertySources) {propertySources.addLast(new StubPropertySource("servletConfigInitParams"));
        propertySources.addLast(new StubPropertySource("servletContextInitParams"));
        if (JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {propertySources.addLast(new JndiPropertySource("jndiProperties"));
        }

        super.customizePropertySources(propertySources);
    }

    public void initPropertySources(@Nullable ServletContext servletContext, @Nullable ServletConfig servletConfig) {WebApplicationContextUtils.initServletPropertySources(this.getPropertySources(), servletContext, servletConfig);
    }
}

public class StandardEnvironment extends AbstractEnvironment {/** System environment property source name: {@value}. */
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";

    /** JVM system properties property source name: {@value}. */
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";

    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }

}

public abstract class AbstractEnvironment implements ConfigurableEnvironment {
    // 省略其余
    public AbstractEnvironment() {customizePropertySources(this.propertySources);
    }
}

能够看进去默认,创立的时候调用了父类的构造函数,配置文件次要增加了如下:

1)servletConfigInitParams、servletContextInitParams,从名字上看,咱们能够连蒙带猜下,stub 应该是示意是桩,可能是预留存储 servlet 相干配置用的,默认都是空的值。jndiProperties 配置依据条件加载,默认没有加载。它配置的值默认是空的 Object()

2)systemEnvironment 和 systemProperties 配置,从名字能够猜出来,是零碎属性和零碎环境变量相干配置, 配置的值解析后为 Map

最终将所有的配置解析好放入到了 List<PropertySource> 中。

也就是执行完创立,此时曾经相当于构建了 4 个配置对象了,也就是 4 个 PropertySource 了。如下图所示:

你可能有一个纳闷,这些不同的配置是怎么来的?

其实,你仔细思考下,你会发现,这些配置有的是本人构建的,有的是从零碎环境变量加载的,有的是从配置文件读取的。

通过形象了一个接口 PropertySource。定义不同的实现,来实现配置文件的解析,这个设计思维值得咱们学习,这就是经典的 java 面向接口的多态编程思维。

目前为止配置文件解决创立就实现了:

ConfigurableEnvironment 其余的添砖加瓦操作

创立了 ConfigurableEnvironment 之后,执行了其余的一些办法。外围思路次要是给 ConfigurableEnvironment 设置其余的一些属性,比方转换器、profile,在减少一些 PropertySource 而已。

    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
            ApplicationArguments applicationArguments) {
        //1. 创立 Environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        //2.ConfigurableEnvironment 其余的添砖加瓦操作
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        ConfigurationPropertySources.attach(environment);
        listeners.environmentPrepared(environment);
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        ConfigurationPropertySources.attach(environment);
        return environment;
    }

下面代码的这些添砖加瓦的我就不认真介绍了 ,次要就是设置一些属性,或者增加了配置文件对象。 间接给大家画一个图概括下就好了,次要逻辑如下图红色箭头所示:

通过上图,能够看到,次要执行的逻辑,具体如下:

1)configureEnvironment

​ a) environment.setConversionService 设置(默认转换器 Service)ApplicationConversionService 单例初始化,不是重点;

​ b) configurePropertySources() 命令行参数的笼罩一些属性,默认不传,什么都不做,不是重点;

​ c) configureProfiles() 判断是否有 spring.acitve.profiles,默认没有指定,设置 activeprifles(List)就是空。这个值得理解下,启动时的参数如果减少了这个,在这里就会失效的,如 spring.acitve.profiles=dev,prod,这里会记录下。activeprifles=dev,prod。(作用的话,其实是用于之后会额定补充加载配置文件,也就是补充 PropertySource,这个一会儿在 listeners.environmentPrepared 剖析中会看到的。)

2)ConfigurationPropertySources.attach(environment) 增加 一套配置配置文件到 List 结尾,也就是默认增加了一个 configurationProperties 名字的 PropertySource,或者说增加了一个 Environment,因为 Environment 封装了 PropertySource,这个配置从正文上看,可能是为了动静的增加和配置一些值用的,晓得 attach 是补充了一个配置文件的对象封装就能够了,其余的 临时不是重点;

3)listeners.environmentPrepared(environment),事件公布 Listener 公布一个环境事件 ApplicationEnvironmentPreparedEvent。配置文件的解决的扩大点外围做了些什么?按事件筛选过的 listener,轮流进行一堆事件处理(和之前公布 ApplicationStartEvent 事件一样的机制),重要逻辑,补充了配置文件

ConfigFileApplicationListener 加载配置的外围解决,很要害,通过Loader,propertySourceLoaders 解析 property(properties,xml)yaml 文件的(yml,yaml)从而补充了新的 PropertySource。

AnsiOutputApplicationListener ANSI 字符编码解决

LoggingApplicationListener 日志相干

ClasspathLoggingApplicationListener 什么都没做

Backgroundpreinitializer 什么都没做

DelegatingApplicationListener 什么都没做

FileEncodingApplicationListener 编码相干,不重要

4)bindToSpringApplication(environment) 设置 environment 到 SpringApplication (然而理论没有设置上 … 比拟奇怪,不重要的逻辑,不深究了),不是重点;

5)EnvironmentConverter 如果 environment 不是 webApplicationType 指定环境配置,这里转换成 StandardServletEnvironment(默认 SERVLET)之前曾经是,所以之类不转换了,不是重点;

6)ConfigurationPropertySources.attach(environment) 再次 attach 放心转换后,这个属性不在了,不是重点;

通过下面的图和阐明,根本解释了 ConfigurableEnvironment 的各种添砖加瓦的操作。其实如果你抓大放小其实这些都不是算是要害,真正的要害就是创立和封装了 ConfigurableEnvironment,另一个比拟重要的就是执行扩大点了。也就是执行 listeners.environmentPrepared(environment), 补充了 Spring 在 ClassPath 下的相干配置文件对象

小结

明天咱们次要剖析了 SpringBoot 在启动过程中:

1)配置文件对象的形象封装如何设计的

2)listeners 扩大点,对配置文件的解决做了扩大,补充了 Spring 在 ClassPath 下的相干配置文件对象

本文由博客一文多发平台 OpenWrite 公布!

退出移动版