SpringBoot配置的加載流程詳細分析
在上一篇Springboot啟動流程解析中我們沒有張開對配置的解析,因為篇幅過大,需要單獨在起一篇文章來講。
且看一下兩行代碼:
ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
第一行代碼是對控制臺入參做了解析順著代碼跟進去我們發(fā)現(xiàn)
先調用SimpleCommandLineArgsParser來解析對應的控制臺入參,解析完之后在丟給父類進行處理
public SimpleCommandLinePropertySource(String... args) {
super(new SimpleCommandLineArgsParser().parse(args));
}
所以接下來我們看這個parse方法:
public CommandLineArgs parse(String... args) {
//構建個緩存,將解析的參數(shù)分別放入兩個容器中,分為兩種類型的熟悉選項參數(shù)和非選項參數(shù),選項參數(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;
}
以上代碼就做了件很簡單的事情,將遍歷所有的入參,然后判斷key是否包含"–“如果包含丟到OptionArg 中 ,不包含就丟到NonOptionArg中,并且將commandLineArgs返回。就做了個分類動作含”–"的寫法標準注解也給出來了 ,必須按下面這種寫法
--foo
--foo=bar
--foo="bar then baz"
--foo=bar,baz,biz
好此時我們已經獲得了一個分好類的參數(shù)對象,丟給父類在加工,發(fā)現(xiàn)父類又丟給了父類,但是我們發(fā)現(xiàn) 父類已經是一個PropertySource的子類,所以最后這里被封裝成了一個PropertySource對象,實際上就是把返回的commandLineArgs對象緩存起來,最終通過提供的抽象方法,可以獲取到對應的屬性。
public CommandLinePropertySource(T source) {
super(COMMAND_LINE_PROPERTY_SOURCE_NAME, source);
}
所以我們回過頭看SimpleCommandLinePropertySource 和DefaultApplicationArguments即可。從簡單的代碼上我們可以很容易看出來,無非最后就是從兩個集合當中取key value,所以直接把它當做一個map就好了,代碼也不貼了。
接著看第二行代碼,這個才是我們的主菜
發(fā)現(xiàn)沒有,寫代碼的層次結構,思維思想,都是一個模式
一個復雜的過程就是 先prepare -->init -->createA–>creatB–>complete.優(yōu)秀的人寫代碼就跟寫文章一樣。一看就很好懂得那種。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//進來第一步先聲明一個可配置的環(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;
}
針對以上代碼我們接著一行行展開創(chuàng)建,這里就是根據不同容器創(chuàng)建不同的對象。
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();
}
}
根據Environment的繼承關系我們不難看出在對象創(chuàng)建時就調用了customizePropertySources(this.propertySources);方法
此時也就是往容器中初始化了兩個對象 一個時 system一個時env,這兩個變量的參數(shù)就是系統(tǒng)和jvm級別的變量對象
@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出來的,通過查看這個類我們可很清楚的知道它也是一個數(shù)據存儲結構,緩存了一個Propertysource的列表。剩下的就是對它的增刪改等處理操作。
所以我們來看這一行
configureEnvironment(environment,applicationArguments.getSourceArgs());
在這段代碼中放了個類型轉換服務,這個類型轉換服務,也是一整套的體系,內置了各種各樣的類型轉換,比如你在配置文件寫了個 時間 100ms 它到底是怎么被識別成100毫秒的,都是通過這個類型轉換服務轉換的。有興趣可以自行拓展開
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對象
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));
}
}
}
從代碼可以看出,獲取了一個defaultProperties 的map把它也加入到list中。而這個map也是在main函數(shù)進行設置的,而這個屬性是出了系統(tǒng)屬性的之外最早加載的propertysource對象
public static void main(String[] args) {
SpringApplicationBuilder builder = new SpringApplicationBuilder();
builder.properties(map);
builder.run(Application.class,args);
}
然后我們回過頭來看configureProfiles方法,此方法等以上配置完成之后,先從配置中抽取出profiles 并將其作為單獨的屬性設置回去。抽取規(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 ,如果取到則終止,也就給我們營造了一個假象,就是同一個配置被覆蓋的假象。不是真真的被覆蓋,而是放在不同的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án)境的變量覆蓋,比如在項目中配置了配置中心為 測試環(huán)境,發(fā)布到生產是不是可以使用環(huán)境變量放在它的上層,就達到覆蓋效果。而不用在打包的時候去改配置。
關于propertysource總體數(shù)據結構體系設計下回分解。
到此這篇關于SpringBoot配置的加載流程詳細分析的文章就介紹到這了,更多相關SpringBoot配置加載過程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
如何使用@ConditionalOnExpression決定是否生效注釋
這篇文章主要介紹了如何使用@ConditionalOnExpression決定是否生效注釋的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
Java之Springcloud Gateway內置路由案例講解
這篇文章主要介紹了Java之Springcloud Gateway內置路由案例講解,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-08-08
SpringBoot整合Minio實現(xiàn)文件上傳和讀取功能
最近有一個需求是關于視頻上傳播放的,需要設計一個方案,中間談到了Minio這個技術,于是來學習一下,所以本文給大家介紹了SpringBoot整合Minio實現(xiàn)文件上傳和讀取功能,文中有詳細的代碼示例供大家參考,需要的朋友可以參考下2024-07-07
SpringBoot使用Editor.md構建Markdown富文本編輯器示例
這篇文章主要介紹了SpringBoot使用Editor.md構建Markdown富文本編輯器示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
SpringBoot中yml多環(huán)境配置的3種方法
這篇文章主要給大家介紹了SpringBoot中yml多環(huán)境配置的3種方法,文中有詳細的代碼示例供大家參考,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2023-10-10

