以前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的处理,没有再详细一步步分析。若是全弄出来,这篇文章就又臭又长了。阅读源码毕竟是个自己学习体会的过程,只阅读文章是不行的,得动手实践。