上一节,咱们次要理解了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 公布!