SpringBoot配置的加載流程詳細(xì)分析
在上一篇Springboot啟動(dòng)流程解析中我們沒有張開對(duì)配置的解析,因?yàn)槠^大,需要單獨(dú)在起一篇文章來講。
且看一下兩行代碼:
ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
第一行代碼是對(duì)控制臺(tái)入?yún)⒆隽私馕鲰樦a跟進(jìn)去我們發(fā)現(xiàn)
先調(diào)用SimpleCommandLineArgsParser來解析對(duì)應(yīng)的控制臺(tái)入?yún)?,解析完之后在丟給父類進(jìn)行處理
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
所以接下來我們看這個(gè)parse方法:
public CommandLineArgs parse(String... args) {
//構(gòu)建個(gè)緩存,將解析的參數(shù)分別放入兩個(gè)容器中,分為兩種類型的熟悉選項(xiàng)參數(shù)和非選項(xiàng)參數(shù),選項(xiàng)參數(shù)使用 --開頭
CommandLineArgs commandLineArgs = new CommandLineArgs();
for (String arg : args) {
if (arg.startsWith("--")) {
String optionText = arg.substring(2, arg.length());
String optionName;
String optionValue = null;
if (optionText.contains("=")) {
optionName = optionText.substring(0, optionText.indexOf('='));
optionValue = optionText.substring(optionText.indexOf('=')+1, optionText.length());
}
else {
optionName = optionText;
}
if (optionName.isEmpty() || (optionValue != null && optionValue.isEmpty())) {
throw new IllegalArgumentException("Invalid argument syntax: " + arg);
}
commandLineArgs.addOptionArg(optionName, optionValue);
}
else {
commandLineArgs.addNonOptionArg(arg);
}
}
return commandLineArgs;
}
以上代碼就做了件很簡單的事情,將遍歷所有的入?yún)?,然后判斷key是否包含"–“如果包含丟到OptionArg 中 ,不包含就丟到NonOptionArg中,并且將commandLineArgs返回。就做了個(gè)分類動(dòng)作含”–"的寫法標(biāo)準(zhǔn)注解也給出來了 ,必須按下面這種寫法
--foo
--foo=bar
--foo="bar then baz"
--foo=bar,baz,biz
好此時(shí)我們已經(jīng)獲得了一個(gè)分好類的參數(shù)對(duì)象,丟給父類在加工,發(fā)現(xiàn)父類又丟給了父類,但是我們發(fā)現(xiàn) 父類已經(jīng)是一個(gè)PropertySource的子類,所以最后這里被封裝成了一個(gè)PropertySource對(duì)象,實(shí)際上就是把返回的commandLineArgs對(duì)象緩存起來,最終通過提供的抽象方法,可以獲取到對(duì)應(yīng)的屬性。
public CommandLinePropertySource(T source) {
super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
}
所以我們回過頭看SimpleCommandLinePropertySource 和DefaultApplicationArguments即可。從簡單的代碼上我們可以很容易看出來,無非最后就是從兩個(gè)集合當(dāng)中取key value,所以直接把它當(dāng)做一個(gè)map就好了,代碼也不貼了。
接著看第二行代碼,這個(gè)才是我們的主菜
發(fā)現(xiàn)沒有,寫代碼的層次結(jié)構(gòu),思維思想,都是一個(gè)模式
一個(gè)復(fù)雜的過程就是 先prepare -->init -->createA–>creatB–>complete.優(yōu)秀的人寫代碼就跟寫文章一樣。一看就很好懂得那種。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//進(jìn)來第一步先聲明一個(gè)可配置的環(huán)境變量容器
ConfigurableEnvironment environment = getOrCreateEnvironment();
//然后配置它
configureEnvironment(environment, applicationArguments.getSourceArgs());
//然后發(fā)布給事件出去告訴所有l(wèi)istener 環(huán)境配置完成
listeners.environmentPrepared(environment);
//把環(huán)境綁定給springboot
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
針對(duì)以上代碼我們接著一行行展開創(chuàng)建,這里就是根據(jù)不同容器創(chuàng)建不同的對(duì)象。
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();
}
}
根據(jù)Environment的繼承關(guān)系我們不難看出在對(duì)象創(chuàng)建時(shí)就調(diào)用了customizePropertySources(this.propertySources);方法
此時(shí)也就是往容器中初始化了兩個(gè)對(duì)象 一個(gè)時(shí) system一個(gè)時(shí)env,這兩個(gè)變量的參數(shù)就是系統(tǒng)和jvm級(jí)別的變量對(duì)象
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
而傳入的MutablePropertySources 直接由父類抽象類直接new出來的,通過查看這個(gè)類我們可很清楚的知道它也是一個(gè)數(shù)據(jù)存儲(chǔ)結(jié)構(gòu),緩存了一個(gè)Propertysource的列表。剩下的就是對(duì)它的增刪改等處理操作。
所以我們來看這一行
configureEnvironment(environment,applicationArguments.getSourceArgs());
在這段代碼中放了個(gè)類型轉(zhuǎn)換服務(wù),這個(gè)類型轉(zhuǎn)換服務(wù),也是一整套的體系,內(nèi)置了各種各樣的類型轉(zhuǎn)換,比如你在配置文件寫了個(gè) 時(shí)間 100ms 它到底是怎么被識(shí)別成100毫秒的,都是通過這個(gè)類型轉(zhuǎn)換服務(wù)轉(zhuǎn)換的。有興趣可以自行拓展開
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
if (this.addConversionService) {
ConversionService conversionService = ApplicationConversionService
.getSharedInstance();
environment.setConversionService(
(ConfigurableConversionService) conversionService);
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
接著往里走
configurePropertySources(environment, args);
這里代碼還是將拿到上面配置的緩存往里面在塞propertysource對(duì)象
protected void configurePropertySources(ConfigurableEnvironment environment,
String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
sources.addLast(
new MapPropertySource("defaultProperties", this.defaultProperties));
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(new SimpleCommandLinePropertySource(
"springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
從代碼可以看出,獲取了一個(gè)defaultProperties 的map把它也加入到list中。而這個(gè)map也是在main函數(shù)進(jìn)行設(shè)置的,而這個(gè)屬性是出了系統(tǒng)屬性的之外最早加載的propertysource對(duì)象
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder();
builder.properties(map);
builder.run(Application.class,args);
}
然后我們回過頭來看configureProfiles方法,此方法等以上配置完成之后,先從配置中抽取出profiles 并將其作為單獨(dú)的屬性設(shè)置回去。抽取規(guī)則看如下代碼
protected Set<String> doGetActiveProfiles() {
synchronized (this.activeProfiles) {
if (this.activeProfiles.isEmpty()) {
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}
最終所有的getProperty都走到如下代碼,而這段代碼也很簡單就是遍歷所有的propertysource ,如果取到則終止,也就給我們營造了一個(gè)假象,就是同一個(gè)配置被覆蓋的假象。不是真真的被覆蓋,而是放在不同的propertysource中,并且propertysource有順序而已。
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
通過以上的代碼分析我們可以知道一件事情放在越底層的propertysource 會(huì)被上層的覆蓋,通過巧妙的利用這一點(diǎn),我們就以通過不同入?yún)⒎绞竭M(jìn)行不同環(huán)境的變量覆蓋,比如在項(xiàng)目中配置了配置中心為 測試環(huán)境,發(fā)布到生產(chǎn)是不是可以使用環(huán)境變量放在它的上層,就達(dá)到覆蓋效果。而不用在打包的時(shí)候去改配置。
關(guān)于propertysource總體數(shù)據(jù)結(jié)構(gòu)體系設(shè)計(jì)下回分解。
到此這篇關(guān)于SpringBoot配置的加載流程詳細(xì)分析的文章就介紹到這了,更多相關(guān)SpringBoot配置加載過程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java深入淺出掌握SpringBoot之MVC自動(dòng)配置原理篇
在進(jìn)行項(xiàng)目編寫前,我們還需要知道一個(gè)東西,就是SpringBoot對(duì)我們的SpringMVC還做了哪些配置,包括如何擴(kuò)展,如何定制,只有把這些都搞清楚了,我們在之后使用才會(huì)更加得心應(yīng)手2021-10-10
解決SpringMvc中普通類注入Service為null的問題
這篇文章主要介紹了解決SpringMvc中普通類注入Service為null的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07

