以前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類太大了不放上來了只講其中一些關鍵點。
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的處理,沒有再詳細一步步分析。若是全弄出來,這篇文章就又臭又長了。閱讀源碼畢竟是個自己學習體會的過程,只閱讀文章是不行的,得動手實踐。