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; }
以上代碼就做了件很簡(jiǎn)單的事情,將遍歷所有的入?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即可。從簡(jiǎn)單的代碼上我們可以很容易看出來,無非最后就是從兩個(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都走到如下代碼,而這段代碼也很簡(jiǎn)單就是遍歷所有的propertysource ,如果取到則終止,也就給我們營(yíng)造了一個(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)目中配置了配置中心為 測(cè)試環(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)文章
如何使用@ConditionalOnExpression決定是否生效注釋
這篇文章主要介紹了如何使用@ConditionalOnExpression決定是否生效注釋的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06Java使用poi生成word文檔的簡(jiǎn)單實(shí)例
Java POI是一個(gè)用于處理Microsoft Office文件(如Word、Excel和PowerPoint)的API,它是一個(gè)開源庫,允許Java開發(fā)者讀取、創(chuàng)建和修改這些文檔,本文給大集介紹了Java使用poi生成word文檔的簡(jiǎn)單實(shí)例,感興趣的朋友可以參考下2024-06-06關(guān)于Mybatis-Plus?Update更新策略問題
這篇文章主要介紹了關(guān)于Mybatis-Plus?Update更新策略問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Java之Springcloud Gateway內(nèi)置路由案例講解
這篇文章主要介紹了Java之Springcloud Gateway內(nèi)置路由案例講解,本篇文章通過簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能
最近有一個(gè)需求是關(guān)于視頻上傳播放的,需要設(shè)計(jì)一個(gè)方案,中間談到了Minio這個(gè)技術(shù),于是來學(xué)習(xí)一下,所以本文給大家介紹了SpringBoot整合Minio實(shí)現(xiàn)文件上傳和讀取功能,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-07-07SpringBoot使用Editor.md構(gòu)建Markdown富文本編輯器示例
這篇文章主要介紹了SpringBoot使用Editor.md構(gòu)建Markdown富文本編輯器示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-03-03SpringBoot中yml多環(huán)境配置的3種方法
這篇文章主要給大家介紹了SpringBoot中yml多環(huán)境配置的3種方法,文中有詳細(xì)的代碼示例供大家參考,對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下2023-10-10