SpringBoot原始碼解析-配置檔案的載入
一般框架,啟動之後都會盡快載入配置檔案,springboot也不例外,下面就開始分析一下springboot載入配置檔案的流程。
springboot配置的載入是從listener類開始的,還記得上一節我說listener類的呼叫沒那麼簡單麼,這一節就先從listener類的呼叫開始。
run方法中,listeners初始化的地方。
public ConfigurableApplicationContext run(String... args) { ... SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); ... } 複製程式碼
listener類在SpringApplication物件初始化的時候,我們已經從配置檔案中獲取到了,並存放在了集合裡,那麼這邊為什麼沒有直接呼叫而是又繞了一個邏輯呢,先進入getRunListeners方法。
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances( SpringApplicationRunListener.class, types, this, args)); } 複製程式碼
getRunListeners方法中,首先獲取了SpringApplicationRunListener物件,並且使用SpringApplication,和args作為的建構函式的引數。然後在使用獲得的SpringApplicationRunListener物件集合作為引數,構造了SpringApplicationRunListeners物件。 我們先去看看SpringApplicationRunListener物件是啥。
getSpringFactoriesInstances這個方法大家 應該很熟了,從SpringApplication物件新建時候就一直在呼叫,所以我們可以直接到配置檔案中看一下,獲取的SpringApplicationRunListener物件到底是啥。
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered { public EventPublishingRunListener(SpringApplication application, String[] args) { this.application = application; this.args = args; this.initialMulticaster = new SimpleApplicationEventMulticaster(); for (ApplicationListener<?> listener : application.getListeners()) { this.initialMulticaster.addApplicationListener(listener); } } 複製程式碼
在配置檔案中發現了EventPublishingRunListener物件,這就是getRunListeners方法中獲得到的SpringApplicationRunListener物件,建構函式很簡單,我就不詳細分析了。原本SpringApplication類中的listener物件,現在被封裝到了EventPublishingRunListener物件中。
回過頭來再看,SpringApplicationRunListener類又被封裝到了SpringApplicationRunListeners物件中,這樣getRunListeners方法的邏輯就執行完了。
現在看看listeners.starting()方法的呼叫邏輯。
public void starting() { for (SpringApplicationRunListener listener : this.listeners) { //遍歷呼叫starting方法 listener.starting(); } } public void starting() { //這個地方封裝了一個事件,大概猜一下應該是打算使用策略模式 this.initialMulticaster.multicastEvent( new ApplicationStartingEvent(this.application, this.args)); } public void multicastEvent(ApplicationEvent event) { multicastEvent(event, resolveDefaultEventType(event)); } public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) { ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event)); //getApplicationListeners獲取了符合策略的監聽器 for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) { Executor executor = getTaskExecutor(); if (executor != null) { executor.execute(() -> invokeListener(listener, event)); } else { invokeListener(listener, event); } } } protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) { ... doInvokeListener(listener, event); ... } private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) { ... listener.onApplicationEvent(event); ... } 複製程式碼
最後終於在doInvokeListener方法中,看到了監聽器的執行。所有監聽器的執行都使用的策略模式,如果想符合某些事件,在監聽器的onApplicationEvent方法中配置一下即可。在這兒,我們也可以感受到spring框架設計的規範性,使用策略模式可以很方便的基於事件做相應擴充套件。
上面我們已經瞭解了listener類的啟動邏輯,下面開始正式分析配置檔案的載入。
public ConfigurableApplicationContext run(String... args) { ... ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); ... } private ConfigurableEnvironment prepareEnvironment( SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { ... listeners.environmentPrepared(environment); ... } 複製程式碼
在run方法中,找到prepareEnvironment方法,進入之後,會看到監聽器啟動了environmentPrepared事件,所以我們就去監聽器裡面,找找看符合環境事件的監聽器。
看名字也能看出來,就是他ConfigFileApplicationListener。找到他的onApplicationEvent方法,開始分析。
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { //入口在這兒 onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { //雖然這邊還有其他幾個監聽器,但是最重要的依然是他本身所以,我們還是分析他本身的postProcessEnvironment方法 postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } 複製程式碼
postProcessEnvironment方法邏輯如下
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) { addPropertySources(environment, application.getResourceLoader()); } protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); //關鍵程式碼在這兒 new Loader(environment, resourceLoader).load(); } //先看建構函式 Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.environment = environment; this.placeholdersResolver = new PropertySourcesPlaceholdersResolver( this.environment); this.resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader(); //這個方法又見到了,話不多說,開啟配置檔案 this.propertySourceLoaders = SpringFactoriesLoader.loadFactories( PropertySourceLoader.class, getClass().getClassLoader()); } //這就是yml和properties配置支援的來源 org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader 複製程式碼
Loader類在建構函式中獲取了yml和properties配置檔案的支援。下面開始分析load函式。
public void load() { ... //前面是關於profile的配置邏輯不復雜應該可以看懂,關鍵方法是load load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true)); ... } private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { //關注getSearchLocations方法 getSearchLocations().forEach((location) -> { boolean isFolder = location.endsWith("/"); //關注getSearchNames方法 Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES; names.forEach( (name) -> load(location, name, profile, filterFactory, consumer)); }); } 複製程式碼
在getSearchLocations方法中,可以看到如果沒有指定地址的話,預設地址就是"classpath:/,classpath:/config/,file:./,file:./config/",如果想指定的話,啟動時需要加上spring.config.location引數
在getSearchNames方法中,可以看到如果沒有指定配置檔名稱的話,配置檔案的名字就按照application來搜尋。如果想指定的話,啟動時需要加上spring.config.name引數
所以繼續往下看load方法
private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) { ... Set<String> processed = new HashSet<>(); for (PropertySourceLoader loader : this.propertySourceLoaders) { for (String fileExtension : loader.getFileExtensions()) { if (processed.add(fileExtension)) { loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory, consumer); } } } } 複製程式碼
在這一層的load方法中,看到了Loader類新建時,獲取的yml和properties格式支援類propertySourceLoaders,檢視兩個類的getFileExtensions方法
public class YamlPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] { "yml", "yaml" }; } public class PropertiesPropertySourceLoader implements PropertySourceLoader { private static final String XML_FILE_EXTENSION = ".xml"; @Override public String[] getFileExtensions() { return new String[] { "properties", "xml" }; } 複製程式碼
到了這一步,我們終於摸清了為什麼預設的配置檔名字必須是application,而且可以為yml和properties格式。
最後載入的過程其實沒啥好分析的了。經過了我們的一通操作,我們已經順利的摸清了springboot預設配置載入的來源,並且瞭解瞭如果想指定配置該怎麼做。