SpringBoot啓動流程---默認ServerProperties的注入源碼分析

以前spring加載properties文件的方式有兩種,一種使用註解@PropertySource引入,一種使用xml配置引入<context:property-placeholder location=""/>,詳細可以看下面的文章spring加載properties文件

這篇的主題是SpringBoot是怎樣實現讀取properties屬性,並自動賦值到相對應Properties類上的。
首先猜測注入的大概流程,然後再驗證。
屬性配置類可以被Spring管理,肯定是以Bean的形式,那麼配置文件的注入肯定發生在Bean的生成過程,判斷Bean上是否有@ConfigurationProperties註解,若發現註解就調用註解對應的解析方法,將配置賦值到配置類的字段上。

properties文件的引入

SpringBoot是有默認名字的properties,啓動時會根據默認名字去classpath上尋找文件。
Spring Boot的啓動是執行SpringApplication.run方法,properties文件的載入是在prepareEnvironment方法中執行。

ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
			ApplicationArguments applicationArguments) {
		// Create and configure the environment
		ConfigurableEnvironment environment = getOrCreateEnvironment();
		configureEnvironment(environment, applicationArguments.getSourceArgs());
		ConfigurationPropertySources.attach(environment);
		
		listeners.environmentPrepared(environment);//<<-------------1
		biUndToSprinUgApplication(environment);
		if (!this.isCustomEnvironment) {
			environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
					deduceEnvironmentClass());
		}
		ConfigurationPropertySources.attach(environment);
		return environment;
	}
public void environmentPrepared(ConfigurableEnvironment environment) {
		for (SpringApplicationRunListener listener : this.listeners) {
			listener.environmentPrepared(environment);//<<-------------2
		}
	}
@Override
	public void environmentPrepared(ConfigurableEnvironment environment) {
		this.initialMulticaster
				.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
	}

	@Override
	public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {//<<-------------3
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);//<<-------------4
			}
		}
	}

通過跟蹤調試可以知道是在listeners.environmentPrepared(environment)中完成;
listeners中此時只有一種類型,org.springframework.boot.context.event.EventPublishingRunListener
通過發佈ApplicationEnvironmentPreparedEvent事件,來執行配置注入。

在代碼中‘//<<-------------3’處getApplicationListeners(event, type)可以獲取到啓動文件的監聽器。
在這裏插入圖片描述
invokeListener實際執行的是onApplicationEvent方法。ConfigFileApplicationListener既實現了Listener也實現了EnvironmentPostProcessor功能。
class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered
ConfigFileApplicationListener 中定義了靜態變量值。比如默認路徑DEFAULT_SEARCH_LOCATIONS 默認全局配置文件DEFAULT_NAMES

	private static final String DEFAULT_PROPERTIES = "defaultProperties";

	// Note the order is from least to most specific (last one wins)
	private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

	private static final String DEFAULT_NAMES = "application";

	private static final Set<String> NO_SEARCH_NAMES = Collections.singleton(null);

	private static final Bindable<String[]> STRING_ARRAY = Bindable.of(String[].class);

	/**
	 * The "active profiles" property name.
	 */
	public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";

	/**
	 * The "includes profiles" property name.
	 */
	public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";

	/**
	 * The "config name" property name.
	 */
	public static final String CONFIG_NAME_PROPERTY = "spring.config.name";

	/**
	 * The "config location" property name.
	 */
	public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";

	/**
	 * The "config additional location" property name.
	 */
	public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
@Override
	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationEnvironmentPreparedEvent) {
			onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);//<<-------------5
		}
		if (event instanceof ApplicationPreparedEvent) {
			onApplicationPreparedEvent(event);
		}
	}
		private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
		List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
		postProcessors.add(this);
		AnnotationAwareOrderComparator.sort(postProcessors);
		//<<-------------6
		for (EnvironmentPostProcessor postProcessor : postProcessors) {
			postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
		}
	}
@Override
	public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
		//<<-------------7
		addPropertySources(environment, application.getResourceLoader());
	}
	protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
		RandomValuePropertySource.addToEnvironment(environment);
		new Loader(environment, resourceLoader).load();//<<-------------8
	}


loadPostProcessors();執行後postProcessors中存在四個後置處理器。其中類本身實現了postProcessor.

在這裏插入圖片描述

在上面的第8步是這樣一行new Loader(environment, resourceLoader).load();Loader是內部類,執行load方法。Loader類太大了不放上來了只講其中一些關鍵點。

getSearchLocations()得出的結果
load方法

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
			getSearchLocations().forEach((location) -> {
				boolean isFolder = location.endsWith("/");
				Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
				names.forEach((name) -> load(location, name, profile, filterFactory, consumer));//<<-------------9
			});
		}

getSearchLocations()得出的結果即爲上面圖中的list,通過forEach執行函數式方法。getSearchNames()返回爲默認值’application’。通過此方法最終會加載到我們的配置文件,並且讀取其中的值放入env中。這一段加載的代碼太長了不分析了,知道結果即可。(源碼閱讀起來層數太多了,寫文檔也好多層,簡直是套娃)
在這裏插入圖片描述

裝載properties類。

裝載properties類呢主要是通過SpringApplicationContext中refresh方法中的registerBeanPostProcessors(beanFactory)裝載,如果對spring的生命週期足夠了解的話可以知道此方法就是將上一步的BeanDefinition包裝成真正的Bean,通過getBean方法可以完成Bean的屬性注入,以及BeanProcessor 前置後置方法。
這裏就不再粘貼詳細代碼了。
處理ConfigurationProperties這個註解的processor是org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor
以ServerProperties爲例,

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties

在這裏插入圖片描述

public void bind(Bindable<?> target) {
		ConfigurationProperties annotation = target.getAnnotation(ConfigurationProperties.class);
		Assert.state(annotation != null, () -> "Missing @ConfigurationProperties on " + target);
		List<Validator> validators = getValidators(target);
		BindHandler bindHandler = getBindHandler(annotation, validators);
		getBinder().bind(annotation.prefix(), target, bindHandler);
	}

最終的最終會調用到

org.springframework.boot.context.properties.bind.Binder#bindBean
private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, BindHandler handler, Context context,
			boolean allowRecursiveBinding) {
		if (containsNoDescendantOf(context.getSources(), name) || isUnbindableBean(name, target, context)) {
			return null;
		}
		BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind(name.append(propertyName),
				propertyTarget, handler, context, false);
		Class<?> type = target.getType().resolve(Object.class);
		if (!allowRecursiveBinding && context.hasBoundBean(type)) {
			return null;
		}
		return context.withBean(type, () -> {
			Stream<?> boundBeans = BEAN_BINDERS.stream().map((b) -> b.bind(name, target, context, propertyBinder));
			return boundBeans.filter(Objects::nonNull).findFirst().orElse(null);
		});
	}

最終會爲Bean初始化好值。
在這裏插入圖片描述

總結:
Bean的配置文件裝載,到屬性注入。原理其實比較簡單,但是實現過程時真的涉及太多步驟了。閱讀源碼真是個體力活啊。最後的屬性注入其實就是對註解ConfigurationProperties的處理,沒有再詳細一步步分析。若是全弄出來,這篇文章就又臭又長了。閱讀源碼畢竟是個自己學習體會的過程,只閱讀文章是不行的,得動手實踐。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章