認真讀完下面的內容我相信會有收穫的
原始碼文章比較枯燥,本文就跟debug呼叫棧一樣一步一步分析原始碼,可能讀起來比較枯燥但是如果堅持讀完肯定會有收穫,建議使用pc版閱讀更舒適
Springboot 配置檔案載入原始碼分析springboot預設配置檔名為application.yml,application.yaml,application.properties當我們在配置檔案檔案中加入配置後重啟springboot,配置資訊如何被springboot識別?本篇文章將從原始碼入手按照如下過程進行分析
springboot監聽器初始化springboot事件釋出器初始化springboot監聽器工作原理Springboot監聽器機制分析springboot透過宣告主類啟動,如下程式碼所示,根據入口函式按圖索驥分析內部原理,入口函式其實很簡單隻呼叫了一個靜態run方法
@SpringBootApplicationpublic class FrameworkApplication { public static void main(String[] args) { SpringApplication.run(FrameworkApplication.class, args); }}
在run方法中隱藏著啟動細節,進入run方法真實邏輯為建立SpringApplication物件並呼叫該物件的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args);}
首先分析SpringApplication建構函式,其功能就是初始化包括如下內容
資源載入器初始化設定啟動主類為primarySources判斷當前應用是否是web應用設定初始化器設定監聽器 (本文重點分析此處邏輯)設定主類public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null"); this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath(); setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class)); setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); this.mainApplicationClass = deduceMainApplicationClass();}
此處重點分析setListeners方法,此方法比較簡單程式碼如下
將傳入的監聽器類賦值給一個List集合private List<ApplicationListener<?>> listeners;public void setListeners(Collection<? extends ApplicationListener<?>> listeners) { this.listeners = new ArrayList<>(listeners);}
ApplicationListener是基於JDK觀察者模式設計的介面 類圖如下ApplicationEvent,EventListener均為JDK中預設介面
檢視完setListeners方法,繼續分析該方法如何呼叫函式getSpringFactoriesInstances,該方法載入type型別的所有class全限定名並進行初始化
SpringFactoriesLoader.loadFactoryNames(type, classLoader) 此方法載入所有jar包中META-INF/spring.factories檔案private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) { ClassLoader classLoader = getClassLoader(); // Use names and ensure unique to protect against duplicates Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader)); List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names); AnnotationAwareOrderComparator.sort(instances); return instances; }
透過springboot的原始碼中發現ApplicationListener類在下圖中有10個其中打購類為配置檔案解析監聽器,這些類透過反射建立完成後傳入setListeners完成賦值操作
到此SpringApplication物件的建立工作結束了,接下來分析run方法中邏輯,由於run方法邏輯比較複雜本文只分析與配置檔案載入相關程式碼
run 方法是springboot啟動的核心包括容器初始化,bean載入等重要邏輯
public ConfigurableApplicationContext run(String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); .....省略程式碼..... try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null); throw new IllegalStateException(ex); } return context;}
SpringApplicationRunListeners 該介面為springboot事件釋出器,其實現類為EventPublishingRunListener類圖如下以下程式碼完成兩件事情
獲取springboot 事件釋出器釋出springboot 容器啟動事件SpringApplicationRunListeners listeners = getRunListeners(args);listeners.starting();
EventPublishingRunListener 中持有SpringApplication物件,該物件持有所有ApplicationListener物件因此可以實現釋出事件到所有監聽器
EventPublishingRunListener starting()方法就是對所有監聽器釋出容器啟動事件,該事件會被獨有監聽器接受並判斷是否處理,處理配置檔案的監聽器ConfigFileApplicationListener也會收到該事件
public void starting() {this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}
ConfigFileApplicationListener處理的事件型別為ApplicationEnvironmentPreparedEvent,ApplicationPreparedEvent,因此ApplicationStartingEvent並不會處理
往下繼續檢視run方法中程式碼會發現prepareEnvironment方法會建立ConfigurableEnvironment類此類是springboot中儲存所有配置的類
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
進入該方法發現事件釋出器向所有堅挺著釋出一個環境準備完畢的事件此事件正式上文提到的ApplicationEnvironmentPreparedEvent因此會觸發ConfigFileApplicationListener處理,但是此時必須已經建立ConfigurableEnvironment類
ConfigFileApplicationListener監聽到ApplicationEnvironmentPreparedEvent事件處理以下幾件事情
載入後置處理器對後置處理器優先順序進行排序呼叫後置處理器處理邏輯後置處理器是所有EnvironmentPostProcessor實現類載入方式與監聽器一樣可參考上文,ConfigFileApplicationListener同樣實現了EnvironmentPostProcessor後置處理器介面因此postProcessors.add(this); 是將自己註冊到所有處理器中實現處理邏輯
loadPostProcessors() {return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,getClass().getClassLoader());}
這裡特此宣告真正實現配置檔案載入的邏輯開始postProcessEnvironment呼叫addPropertySources呼叫Loader(environment, resourceLoader).load(),所有的邏輯處理都是透過Loader 這個內部類來完成的,此處的處理方式跟SpringApplication(primarySources).run(args) 是不是極為相似
最後分析下Loader類的邏輯首先初始化完成
springboot環境類用於存放所有配置檔案中的key=value變數佔位符解析類資源載入器配置檔案解析載入器,處理 properties,yml,yaml檔案的載入PropertySourceLoader實現類為下圖中兩個,因此配置檔案的載入會根據副檔名不同使用不用類來處理
重要的邏輯終於出來了,此處重點邏輯分為
處理spring.profiles.active,spring.profiles.include配置載入配置檔案並與profile繫結將配置檔案與springboot Environment 繫結啟用對應的profileload方法首先會在幾個預設路徑下嘗試載入,預設路徑如下
DEFAULT_SEARCH_LOCATIONS = “classpath:/,classpath:/config/,file:./,file:./config/*/,file:./config/”;
getSearchNames()方法返回配置檔名字預設名字為:application
此處特別說明下如果啟動方式為 java -jar xxx.jar -Dspring.config.name=xx.properties 則不會載入預設名字的配置檔案,網上很多關於spring.config.name使用這裡透過原始碼分析說明了使用方式
接下來是load方法因為name非空則直接進入紅框邏輯,透過副檔名來處理不同型別配置檔案
如果沒有配置spring.profile.active直接進入紅框邏輯
透過resourceLoader載入路徑下配置檔案透過合法行校驗後加載對應配置檔案等待下一步解析
配置檔案載入完畢後進行解析此處consumer lambda方法為addToLoaded並將其與Map<Profile, MutablePropertySources> loaded 繫結
最後是將配置與enviroment 繫結
寫在最後這是一篇原始碼流水賬文章,記錄了怎麼跟springboot原始碼進行配置檔案載入原理分析,如果你跟著我的步驟一步一步debug肯定是有所收穫的,由於篇幅原因Enviroment實現類分析沒有展開