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