目录

  • properties文件的引入
  • 装载properties类。


以前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)可以获取到启动文件的监听器。

SpringBoot中properties配置中增加占位符获取实体属性的值 springboot注入properties_java


invokeListener实际执行的是onApplicationEvent方法。ConfigFileApplicationListener既实现了Listener也实现了EnvironmentPostProcessor功能。

class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, OrderedConfigFileApplicationListener 中定义了静态变量值。比如默认路径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.

SpringBoot中properties配置中增加占位符获取实体属性的值 springboot注入properties_spring boot_02

在上面的第8步是这样一行new Loader(environment, resourceLoader).load();Loader是内部类,执行load方法。Loader类太大了不放上来了只讲其中一些关键点。

SpringBoot中properties配置中增加占位符获取实体属性的值 springboot注入properties_spring boot_03


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中。这一段加载的代码太长了不分析了,知道结果即可。(源码阅读起来层数太多了,写文档也好多层,简直是套娃)

SpringBoot中properties配置中增加占位符获取实体属性的值 springboot注入properties_加载_04

装载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

SpringBoot中properties配置中增加占位符获取实体属性的值 springboot注入properties_java_05

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初始化好值。

SpringBoot中properties配置中增加占位符获取实体属性的值 springboot注入properties_spring boot_06

总结:
Bean的配置文件装载,到属性注入。原理其实比较简单,但是实现过程时真的涉及太多步骤了。阅读源码真是个体力活啊。最后的属性注入其实就是对注解ConfigurationProperties的处理,没有再详细一步步分析。若是全弄出来,这篇文章就又臭又长了。阅读源码毕竟是个自己学习体会的过程,只阅读文章是不行的,得动手实践。