SpringBoot源碼解析-配置文件的加載

通常框架,啓動以後都會盡快加載配置文件,springboot也不例外,下面就開始分析一下springboot加載配置文件的流程。spring


springboot配置的加載是從listener類開始的,還記得上一節我說listener類的調用沒那麼簡單麼,這一節就先從listener類的調用開始。springboot

run方法中,listeners初始化的地方。bash

public ConfigurableApplicationContext run(String... args) {
        ...
		SpringApplicationRunListeners listeners = getRunListeners(args);
		listeners.starting();
        ...
	}
複製代碼

listener類在SpringApplication對象初始化的時候,咱們已經從配置文件中獲取到了,並存放在了集合裏,那麼這邊爲何沒有直接調用而是又繞了一個邏輯呢,先進入getRunListeners方法。app

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對象究竟是啥。dom

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對象中。ide

回過頭來再看,SpringApplicationRunListener類又被封裝到了SpringApplicationRunListeners對象中,這樣getRunListeners方法的邏輯就執行完了。函數

如今看看listeners.starting()方法的調用邏輯。post

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框架設計的規範性,使用策略模式能夠很方便的基於事件作相應擴展。ui


上面咱們已經瞭解了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默認配置加載的來源,而且瞭解了若是想指定配置該怎麼作。


返回目錄

相關文章
相關標籤/搜索