Spring Boot 源码分析(5) -- applicationContext.properties资源文件分析

Spring Boot 源码分析(5) -- applicationContext.properties资源文件分析

在前面分析监听器时,我们提到过ConfigFileApplicationListener,这个监听器在Spring Boot中负责处理applicationContext.properties属性文件的读取。

在Spring Boot 源码分析(2)中我们分析过监听器和监听事件。在环境准备完毕时会触发ApplicationEnvironmentPreparedEvent事件,在上下文加载完成时会触发ApplicationPreparedEvent事件。

在ConfigFileApplicationListener类中,监听的入口在onApplicationEvent(ApplicationEvent event)方法。代码如下:

public void onApplicationEvent(ApplicationEvent event) {
	// 监听环境准备完毕事件
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent(
				(ApplicationEnvironmentPreparedEvent) event);
	}
	// 监听上下文准备完毕事件
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}

监听环境准备完毕

从/META-INF/spirng.fatories中获取EnvironmentPostProcessor实例,当然ConfigFileApplicationListener本身也实现了这个接口。然后处理EnvironmentPostProcessor的postProcessEnvironment方法。很显然这个接口是对环境做后置处理。

private void onApplicationEnvironmentPreparedEvent(
		ApplicationEnvironmentPreparedEvent event) {
	// 从/META-INF/spirng.fatories中获取EnvironmentPostProcessor实例
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	// ConfigFileApplicationListener本身也实现了这个接口
	postProcessors.add(this);
	AnnotationAwareOrderComparator.sort(postProcessors);
	// 环境后置处理
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(),
				event.getSpringApplication());
	}
}

我们看在ConfigFileApplicationListener中对环境的后置处理:

public void postProcessEnvironment(ConfigurableEnvironment environment,
		SpringApplication application) {
	// 添加属性源
	addPropertySources(environment, application.getResourceLoader());
	// 配置忽略的Bean信息
	configureIgnoreBeanInfo(environment);
	// 绑定到SpringApplication
	bindToSpringApplication(environment, application);
}
添加属性源

首先将随机值属性源添加到环境中来,随机值属性源对象为RandomValuePropertySource。然后使用内部类Loader对环境进行加载。由于Loader类分析比较复杂,我们放到后面分析。

protected void addPropertySources(ConfigurableEnvironment environment,
		ResourceLoader resourceLoader) {
	// 将随机值属性添加到环境中
	RandomValuePropertySource.addToEnvironment(environment);
	// 加载器加载属性源
	new Loader(environment, resourceLoader).load();
}
配置忽略Bean信息

可以从系统环境中设置spring.beaninfo.ignore属性来表示是否忽略Bean信息,这个属性是Boolean类型的值。

private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
	// 判断系统属性中是否存在spring.beaninfo.ignore
	if (System.getProperty(
			CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
		// 如果不存在,放宽限制获取前缀为spring.beaninfo.的属性
		RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(environment,
				"spring.beaninfo.");
		// 判断是否存在ignore属性,提供默认值true
		Boolean ignore = resolver.getProperty("ignore", Boolean.class, Boolean.TRUE);
		// 设置到系统属性中去
		System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
				ignore.toString());
	}
}
绑定环境到SpringApplication
protected void bindToSpringApplication(ConfigurableEnvironment environment,
		SpringApplication application) {
	PropertiesConfigurationFactory<SpringApplication> binder = new PropertiesConfigurationFactory<SpringApplication>(
			application);
	binder.setTargetName("spring.main");
	binder.setConversionService(this.conversionService);
	binder.setPropertySources(environment.getPropertySources());
	try {
		binder.bindPropertiesToTarget();
	}
	catch (BindException ex) {
		throw new IllegalStateException("Cannot bind to SpringApplication", ex);
	}
}

内部类Loader分析

在分析Loader之前,我们先对ConfigFileApplicationListener属性域做一些介绍,便于后面理解。

  1. 常量定义
// 定义默认属性值
private static final String DEFAULT_PROPERTIES = "defaultProperties";

// 定义默认的文件搜索路径,优先级从低到高
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

// 默认配置文件名称
private static final String DEFAULT_NAMES = "application";

/**
 * 定义profile行为的属性
 */
public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";

/**
 * 定义profile包含的属性
 */
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";

/**
 * 定义配置文件名称的属性
 */
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";

/**
 * 定义配置路径的属性
 */
public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";

/**
 * 定义Order排序值
 */
public static final int DEFAULT_ORDER = Ordered.HIGHEST_PRECEDENCE + 10;

/**
 * 定义应用配置的属性源名称
 */
public static final String APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME = "applicationConfigurationProperties";
  1. 属性域
// 文件搜索路径
private String searchLocations;
// 配置文件名称
private String names;
// Order排序值
private int order = DEFAULT_ORDER;
Loader属性和构造方法

在Loader中定义了profile信息,以及资源加载器。

  1. 属性
// 环境
private final ConfigurableEnvironment environment;
// 资源加载器
private final ResourceLoader resourceLoader;
// 属性加载器
private PropertySourcesLoader propertiesLoader;
// profile队列
private Queue<Profile> profiles;
// 被处理的profiles
private List<Profile> processedProfiles;
// 处理活动状态的profiles
private boolean activatedProfiles;
  1. 构造方法

构造方法从SpringAppliaction中获取了环境和资源加载器,如果资源加载器为空,则使用默认的加载器DefaultResourceLoader。

Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	this.environment = environment;
	this.resourceLoader = resourceLoader == null ? new DefaultResourceLoader()
			: resourceLoader;
}
Loader#load()方法分析

首先从环境中获取spring.profiles.active和spring.profiles.include这两个配置,存放到initialActiveProfiles集合中去。注意不是从配置文件中,因为在这时还没有加载配置文件。然后通过environment.getActiveProfiles()方法获取已经存在的集合,将此集合和initialActiveProfiles集合做差集,获得未在environment中的profiles添加到profiles队列中去,如果没有则添加默认的profiles。最后添加一个null到队列中去。

public void load() {
	this.propertiesLoader = new PropertySourcesLoader();
	this.activatedProfiles = false;
	this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
	this.processedProfiles = new LinkedList<Profile>();

	// 从环境中获取spring.profiles.active和spring.profiles.include配置的profiles
	Set<Profile> initialActiveProfiles = initializeActiveProfiles();
	// 获取环境中还未处理的profiles
	this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
	if (this.profiles.isEmpty()) {
		// 获取默认profiles
		for (String defaultProfileName : this.environment.getDefaultProfiles()) {
			Profile defaultProfile = new Profile(defaultProfileName, true);
			if (!this.profiles.contains(defaultProfile)) {
				this.profiles.add(defaultProfile);
			}
		}
	}

	// 添加null,null会第一个出队列
	this.profiles.add(null);
	// 循环处理,队列中存在[null, default]
	while (!this.profiles.isEmpty()) {
		Profile profile = this.profiles.poll();
		// 搜索文件路径,以/结尾表示文件夹搜索,否则为文件。
		for (String location : getSearchLocations()) {
			if (!location.endsWith("/")) {
				load(location, null, profile);
			}
			else {
				// 获取需要搜索的文件名称
				for (String name : getSearchNames()) {
					load(location, name, profile);
				}
			}
		}
		this.processedProfiles.add(profile);
	}
	// 添加配置属性到环境中去
	addConfigurationProperties(this.propertiesLoader.getPropertySources());
}

从profiles队列中逐一获取,然后进行文件路径搜索,/结尾的表示文件夹,否则为文件。使用load(location, name, profile)进行文件的加载。加载的属性源文件存放在PropertySourcesLoader中,然后添加到配置属性中去。

getSearchLocations()方法获取需要搜索的文件路径,默认使用的是classpath:/,classpath:/config/,file:./,file:./config/。首先判断环境中是否存在spring.config.location属性配置了指定的路径,如果存在则添加路径到locations中。然后将从searchLocations变量中获取路径,如果没有则使用默认的classpath:/,classpath:/config/,file:./,file:./config/。注意searchLocations可以使用逗号分隔多个路径地址,最后的优先级最高。

private Set<String> getSearchLocations() {
	Set<String> locations = new LinkedHashSet<String>();
	// 判断环境中是否存在spring.config.location属性
	if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {
		// 遍历获取配置路径
		// 如果是文件路径,需要加file:前缀
		for (String path : asResolvedSet(
				this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {
			if (!path.contains("$")) {
				path = StringUtils.cleanPath(path);
				if (!ResourceUtils.isUrl(path)) {
					path = ResourceUtils.FILE_URL_PREFIX + path;
				}
			}
			locations.add(path);
		}
	}
	// asResolvedSet方法会对List数据进行顺序反转
	locations.addAll(
			asResolvedSet(ConfigFileApplicationListener.this.searchLocations,
					DEFAULT_SEARCH_LOCATIONS));
	return locations;
}

​getSearchNames()方法获取需要搜索的文件名称,默认名称是application。首先从环境中获取spring.config.name属性,如果存在则使用环境中配置的文件名称,可以配置多个,用逗号分隔。如果没有则使用默认的文件名称application

private Set<String> getSearchNames() {
	if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {
		return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),
				null);
	}
	return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);
}

在获取到配置的locations之后,对路径中搜索的文件进行加载,使用load(String location, String name, Profile profile)方法。

private void load(String location, String name, Profile profile) {
	// 分组,第一个prifile为空,当location为文件时name为空
	String group = "profile=" + (profile == null ? "" : profile);
	if (!StringUtils.hasText(name)) {
		// 直接加载路径(文件)
		loadIntoGroup(group, location, profile);
	}
	else {
		// 获取支持加载的文件后缀 .properties、.yaml、.yam
		for (String ext : this.propertiesLoader.getAllFileExtensions()) {
			if (profile != null) {
				// 加载profile文件,例如:classpath:/application-dev.properties
				loadIntoGroup(group, location + name + "-" + profile + "." + ext,
						null);
				// 加载被处理过的profile,为什么?
				for (Profile processedProfile : this.processedProfiles) {
					if (processedProfile != null) {
						loadIntoGroup(group, location + name + "-"
								+ processedProfile + "." + ext, profile);
					}
				}
				// Sometimes people put "spring.profiles: dev" in
				// application-dev.yml (gh-340). Arguably we should try and error
				// out on that, but we can be kind and load it anyway.
				// 
				loadIntoGroup(group, location + name + "-" + profile + "." + ext,
						profile);
			}
			// 首先会加载最通用的文件,例如: application.proerties
			loadIntoGroup(group, location + name + "." + ext, profile);
		}
	}
}

从上面分析看,在加载的过程中会对搜索文件路径和支持的后缀文件进行遍历处理,都调用loadIntoGroup方法,添加到分组中去。loadIntoGroup方法只调用了doLoadIntoGroup方法。下面继续分析doLoadIntoGroup方法,看文件加载的过程。

private PropertySource<?> doLoadIntoGroup(String identifier, String location,
		Profile profile) throws IOException {
	// 使用资源加载器获取文件资源
	Resource resource = this.resourceLoader.getResource(location);
	PropertySource<?> propertySource = null;
	StringBuilder msg = new StringBuilder();
	// 如果加载到文件就处理,否则跳过处理
	if (resource != null && resource.exists()) {
		String name = "applicationConfig: [" + location + "]";
		String group = "applicationConfig: [" + identifier + "]";
		// 使用属性源加载器处理资源文件
		propertySource = this.propertiesLoader.load(resource, group, name,
				(profile == null ? null : profile.getName()));
		if (propertySource != null) {
			msg.append("Loaded ");
			// 将获取的属性源中的profiles存放到profiles属性域中。
			handleProfileProperties(propertySource);
		}
		else {
			msg.append("Skipped (empty) ");
		}
	}
	else {
		msg.append("Skipped ");
	}
	msg.append("config file ");
	msg.append(getResourceDescription(location, resource));
	if (profile != null) {
		msg.append(" for profile ").append(profile);
	}
	if (resource == null || !resource.exists()) {
		msg.append(" resource not found");
		this.logger.trace(msg);
	}
	else {
		this.logger.debug(msg);
	}
	return propertySource;
}
PropertySourcesLoader 属性源加载器

​在上面的doLoadIntoGroup(String group, String location,Profile profile)方法中,将搜索到的文件进行分组后使用PropertySourcesLoader的load方法进行处理资源文件。下面我们具体分析下这个类。

先从构造方法分析:

public PropertySourcesLoader() {
	this(new MutablePropertySources());
}

public PropertySourcesLoader(MutablePropertySources propertySources) {
	Assert.notNull(propertySources, "PropertySources must not be null");
	this.propertySources = propertySources;
	this.loaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class,
			getClass().getClassLoader());
}

在Loader#load()方法调用时首先就通过this.propertiesLoader = new PropertySourcesLoader()创建了实例对象。通过上面的构造方法得知this.loaders从/META-INF/spring.fatories中获取PropertySourceLoader实例,这两个实例对象是PropertiesPropertySourceLoader和YamlPropertySourceLoader,分别处理properties文件和yaml文件。

在spring-boot-1.5.8.RELEASE.jar包中的/META-INF/spring.fatories文件中配置如下:

# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader

PropertySourcesLoader类是属性资源加载的工具类,相当于PropertySourceLoader接口的代理层。下面看看load方法。

public PropertySource<?> load(Resource resource, String group, String name,
		String profile) throws IOException {
	// 判断资源是否是文件
	if (isFile(resource)) {
		// 组合成文件名称
		String sourceName = generatePropertySourceName(name, profile);
		// 遍历资源加载器
		for (PropertySourceLoader loader : this.loaders) {
			// 根据后缀判断文件是否可以被加载
			if (canLoadFileExtension(loader, resource)) {
				// 开始加载文件
				PropertySource<?> specific = loader.load(sourceName, resource,
						profile);
				// 将获得的属性源添加到this.propertySources中去
				addPropertySource(group, specific, profile);
				return specific;
			}
		}
	}
	return null;
}

PropertiesPropertySourceLoader加载器负责加载properties、xml文件,YamlPropertySourceLoader加载器负责加载yaml、yam文件。下面是这两个类的getFileExtensions()方法。

public class PropertiesPropertySourceLoader implements PropertySourceLoader {
	@Override
	public String[] getFileExtensions() {
		return new String[] { "properties", "xml" };
	}
}

public class YamlPropertySourceLoader implements PropertySourceLoader {

	@Override
	public String[] getFileExtensions() {
		return new String[] { "yml", "yaml" };
	}
}

分析完了属性资源文件的加载,最后将加载完成的属性源对象保存在PropertySourcesLoader的集合中。在加载完成后,调用addConfigurationProperties方法,将属性源设置到环境中去。

private void addConfigurationProperties(MutablePropertySources sources) {
	List<PropertySource<?>> reorderedSources = new ArrayList<PropertySource<?>>();
	for (PropertySource<?> item : sources) {
		reorderedSources.add(item);
	}
	// 使用ConfigurationPropertySources将所有的属性源进行包装
	addConfigurationProperties(
			new ConfigurationPropertySources(reorderedSources));
}

private void addConfigurationProperties(
		ConfigurationPropertySources configurationSources) {
	MutablePropertySources existingSources = this.environment
			.getPropertySources();
	// 判断环境中是否存在DEFAULT_PROPERTIES这个属性源,如果存在添加到之前
	if (existingSources.contains(DEFAULT_PROPERTIES)) {
		existingSources.addBefore(DEFAULT_PROPERTIES, configurationSources);
	}
	else {
		// 如果不存在添加到最后
		existingSources.addLast(configurationSources);
	}
}

但是属性源的配置还并没有结束,ConfigurationPropertySources统一包装多个属性源只是一个中间过程。后面的处理还需要分析监听上下文准备完毕事件。在上面的处理中只是将ConfigurationPropertySources和DEFAULT_PROPERTIES属性源的位置关系进行处理。

监听上下文准备完毕事件

在ConfigFileApplicationListener中会通过ApplicationPreparedEvent事件来触发onApplicationPreparedEvent(event)方法。方法内容如下:

private void onApplicationPreparedEvent(ApplicationEvent event) {
	this.logger.replayTo(ConfigFileApplicationListener.class);
	// 添加BeanFactoryPostProcessor
	addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());
}
protected void addPostProcessors(ConfigurableApplicationContext context) {
	// 将后置处理器添加到上下文中去
	context.addBeanFactoryPostProcessor(
			new PropertySourceOrderingPostProcessor(context));
}

我们在分析Spring Boot BeanFactoryPostProcessor时提到过ConfigFileApplicationListener$PropertySourceOrderingPostProcessor这个类,就是在监听上下文准备完毕后添加进去的。在Spring启动注册BeanFacatoryPostProcessor时会调用postProcessBeanFactory方法。

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
		throws BeansException {
	reorderSources(this.context.getEnvironment());
}

private void reorderSources(ConfigurableEnvironment environment) {
	// 完成和重新分配属性配置
	ConfigurationPropertySources
			.finishAndRelocate(environment.getPropertySources());
	// 从环境中删除DEFAULT_PROPERTIES
	// 如果存在,则放到最后,也就是说默认的属性优先级最低
	PropertySource<?> defaultProperties = environment.getPropertySources()
			.remove(DEFAULT_PROPERTIES);
	if (defaultProperties != null) {
		environment.getPropertySources().addLast(defaultProperties);
	}
}

关于重新分配属性我们需要细看一下,看配置资源是如何重新分配到环境中去的。下面是ConfigurationPropertySources类中的finishAndRelocate方法。

public static void finishAndRelocate(MutablePropertySources propertySources) {
	// 很显然这里获取的ConfigurationPropertySources就是上面添加到环境中去的
	String name = APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME;
	ConfigurationPropertySources removed = (ConfigurationPropertySources) propertySources
			.get(name);
	// 如果存在,需要做如下处理
	if (removed != null) {
		// 将ConfigurationPropertySources中的属性源集合逐一存放到环境中来
		for (PropertySource<?> propertySource : removed.sources) {
			// 这里处理嵌入的情况,主要是设置name值
			if (propertySource instanceof EnumerableCompositePropertySource) {
				EnumerableCompositePropertySource composite = (EnumerableCompositePropertySource) propertySource;
				for (PropertySource<?> nested : composite.getSource()) {
					propertySources.addAfter(name, nested);
					name = nested.getName();
				}
			}
			else {
				propertySources.addAfter(name, propertySource);
			}
		}
		// 最后从环境中删除
		propertySources.remove(APPLICATION_CONFIGURATION_PROPERTY_SOURCE_NAME);
	}
}

至此,所有的属性配置资源已经加载并存放到环境中来了。

转载于:https://my.oschina.net/xiaoqiyiye/blog/1624285

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值