SpringBoot(三) Environment

Posted by ZhouJ000 on October 8, 2019

SpringBoot(一) 启动与自动配置
SpringBoot(二) starter与servlet容器
SpringBoot(三) Environment
SpringBoot(四) 集成apollo遇到的事儿
SpringBoot(五) 健康检查(上)
SpringBoot(六) 健康检查(下)

Environment

environment Environment接口是暴露出来当前运行环境的公开接口,从接口方法可以看出,主要实现功能分为两个关键部分,Properties属性和Profile环境配置文件

  • PropertyResolver:提供属性访问功能
  • ConfigurablePropertyResolver:继承自PropertyResolver,额外提供属性类型转换(基于org.springframework.core.convert.ConversionService)功能
  • Environment:继承自PropertyResolver,额外提供访问和判断profiles的功能
  • ConfigurableEnvironment:继承自ConfigurablePropertyResolver和Environment,并且提供设置激活的profile和默认的profile的功能
  • ConfigurableWebEnvironment:继承自ConfigurableEnvironment,并且提供配置Servlet上下文和Servlet参数的功能
  • AbstractEnvironment:实现了ConfigurableEnvironment接口,默认属性和存储容器的定义,并且实现了ConfigurableEnvironment中的方法,并且为子类预留可覆盖了扩展方法
  • StandardEnvironment:继承自AbstractEnvironment,非Servlet(Web)环境下的标准Environment实现
  • StandardServletEnvironment:继承自StandardEnvironment,Servlet(Web)环境下的标准Environment实现
  • Reactive相关接口

Environment的静态属性和存储容器都是在AbstractEnvironment中定义的:

private final MutablePropertySources propertySources = new MutablePropertySources();

// Environment的存储容器就是org.springframework.core.env.PropertySource的子类集合
public class MutablePropertySources implements PropertySources {
	private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
	// ...
}

public abstract class PropertySource<T> {
	protected final String name;
	protected final T source;
	public boolean equals(Object other) {
		return (this == other || (other instanceof PropertySource &&
				ObjectUtils.nullSafeEquals(this.name, ((PropertySource<?>) other).name)));
	}
	public int hashCode() {
		return ObjectUtils.nullSafeHashCode(this.name);
	}
	//...
}

从重写的equals和hashCode可以看出,一个PropertySource实例绑定到一个唯一的name,这个name有点像HashMap里面的key,这些说明只和name属性有关。PropertySource的最常用子类是:
1、MapPropertySource:继承自EnumerablePropertySource,source指定为Map实例的PropertySource实现
2、PropertiesPropertySource:继承自MapPropertySource,source指定为Map实例的PropertySource实现,内部的Map实例由Properties实例转换而来
3、ResourcePropertySource:继承自PropertiesPropertySource,source指定为通过Resource实例转化为Properties再转换为Map实例
4、StubPropertySource:PropertySource的一个内部类,source设置为null,实际上就是空实现
5、ComparisonPropertySource:继承自StubPropertySource,所有属性访问方法强制抛出异常,作用就是一个不可访问属性的空实现

再回过头看MutablePropertySources,list创建的是一个CopyOnWriteArrayList,这是配置属性的底层存储数据结构,MutablePropertySources是PropertySources的一个子类,也提供了get、addFirst、addLast、addBefore、addAfter、remove、replace等便捷方法

然后来看AbstractEnvironment中的另一个属性,Environment属性的访问都是委托到PropertySourcesPropertyResolver的:

private final ConfigurablePropertyResolver propertyResolver =
			new PropertySourcesPropertyResolver(this.propertySources);

PropertySourcesPropertyResolver是ConfigurablePropertyResolver的实现,默认的profile就是字符串default

加载配置

在SpringApplication的run方法中,会进行Environment加载:

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 1. Create and configure the environment
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 启动参数绑定到ConfigurableEnvironment中
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	ConfigurationPropertySources.attach(environment);
	// 2. 发布ConfigurableEnvironment准备完毕事件
	listeners.environmentPrepared(environment);
	// 绑定ConfigurableEnvironment到当前的SpringApplication实例中
	bindToSpringApplication(environment);
	// 
	if (!this.isCustomEnvironment) {
		environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
				deduceEnvironmentClass());
	}
	// 绑定ConfigurationPropertySourcesPropertySource到ConfigurableEnvironment中,其name为configurationProperties;实例是SpringConfigurationPropertySources,内部是ConfigurableEnvironment中的MutablePropertySources
	ConfigurationPropertySources.attach(environment);
	return environment;
}

1、看一下getOrCreateEnvironment方法时怎么创建ConfigurableEnvironment的:

private ConfigurableEnvironment getOrCreateEnvironment() {
	if (this.environment != null) {
		return this.environment;
	}
	switch (this.webApplicationType) {
		// SpringMVC项目
		case SERVLET:
			return new StandardServletEnvironment();
		case REACTIVE:
			return new StandardReactiveWebEnvironment();
		default:
			return new StandardEnvironment();
	}
}

2、发布ConfigurableEnvironment准备完毕事件,这里用到了同步的EventBus,事件的监听者是ConfigFileApplicationListener,具体处理逻辑是onApplicationEnvironmentPreparedEvent方法:

// EventPublishingRunListener
public void multicastEvent(ApplicationEvent event) {
	multicastEvent(event, resolveDefaultEventType(event));
}

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


// ConfigFileApplicationListener
public void onApplicationEvent(ApplicationEvent event) {
	if (event instanceof ApplicationEnvironmentPreparedEvent) {
		onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
	}
	if (event instanceof ApplicationPreparedEvent) {
		onApplicationPreparedEvent(event);
	}
}
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
	postProcessors.add(this);
	AnnotationAwareOrderComparator.sort(postProcessors);
	// 遍历所有的EnvironmentPostProcessor对Environment实例进行处理
	/**
		0 = {SystemEnvironmentPropertySourceEnvironmentPostProcessor@2303} 
		1 = {SpringApplicationJsonEnvironmentPostProcessor@2304} 
		2 = {CloudFoundryVcapEnvironmentPostProcessor@2305} 
		3 = {ConfigFileApplicationListener@2235} 
		4 = {SpringBootTestRandomPortEnvironmentPostProcessor@2306} 
	**/
	for (EnvironmentPostProcessor postProcessor : postProcessors) {
		postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
	}
}

List<EnvironmentPostProcessor> loadPostProcessors() {
	return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader());
}

大部分的逻辑处理在ConfigFileApplicationListener中,见其postProcessEnvironment方法:

public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
	addPropertySources(environment, application.getResourceLoader());
}
// 主要的配置环境加载逻辑在内部类Loader,Loader会匹配多个路径下的文件把属性加载到ConfigurableEnvironment中,加载器主要是PropertySourceLoader的实例
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) {
	RandomValuePropertySource.addToEnvironment(environment);
	new Loader(environment, resourceLoader).load();
}

可见其内部类Loader:

public void load() {
	this.profiles = new LinkedList<>();
	this.processedProfiles = new LinkedList<>();
	this.activatedProfiles = false;
	this.loaded = new LinkedHashMap<>();
	initializeProfiles();
	while (!this.profiles.isEmpty()) {
		Profile profile = this.profiles.poll();
		if (profile != null && !profile.isDefaultProfile()) {
			addProfileToEnvironment(profile.getName());
		}
		load(profile, this::getPositiveProfileFilter, addToLoaded(MutablePropertySources::addLast, false));
		this.processedProfiles.add(profile);
	}
	resetEnvironmentProfiles(this.processedProfiles);
	// 加载配置
	load(null, this::getNegativeProfileFilter, addToLoaded(MutablePropertySources::addFirst, true));
	addLoadedPropertySources();
}

private void load(Profile profile, DocumentFilterFactory filterFactory, DocumentConsumer consumer) {
	/**
		0 = "file:./config/"
		1 = "file:./"
		2 = "classpath:/config/"
		3 = "classpath:/"
	**/
	getSearchLocations().forEach((location) -> {
		boolean isFolder = location.endsWith("/");
		Set<String> names = isFolder ? getSearchNames() : NO_SEARCH_NAMES;
		// name = application
		names.forEach((name) -> load(location, name, profile, filterFactory, consumer));
	});
}

比如用到application-${profile}.yaml文件做应用主配置文件,使用的是YamlPropertySourceLoader,这个时候activeProfiles也会被设置到ConfigurableEnvironment中。加载完毕之后,ConfigurableEnvironment中基本包含了所有需要加载的属性,所有属性都是key-value形式存储的(activeProfiles这时被写入ConfigurableEnvironment)

private void load(String location, String name, Profile profile, DocumentFilterFactory filterFactory,
			DocumentConsumer consumer) {
	if (!StringUtils.hasText(name)) {
		for (PropertySourceLoader loader : this.propertySourceLoaders) {
			if (canLoadFileExtension(loader, location)) {
				load(loader, location, profile, filterFactory.getDocumentFilter(profile), consumer);
				return;
			}
		}
	}
	Set<String> processed = new HashSet<>();
	/**
		0 = {PropertiesPropertySourceLoader@2854} 
		1 = {YamlPropertySourceLoader@2855} 
	**/
	for (PropertySourceLoader loader : this.propertySourceLoaders) {
		/**
			0 = "properties"
			1 = "xml"
		**/
		for (String fileExtension : loader.getFileExtensions()) {
			if (processed.add(fileExtension)) {
				loadForFileExtension(loader, location + name, "." + fileExtension, profile, filterFactory,
						consumer);
			}
		}
	}
}

部分配置demo,集成maven和Spring boot的profile:

application.yml:
spring:
  profiles:
    active: @profileActive@
	
或者spring,applicationContext.xml:
<context:property-placeholder location="classpath:${env}/**/*.properties" />	

或者使用maven-assembly-plugin,assembly.xml:
<assembly>
	...
	<fileSet>
		<directory>src/main/resources/${env}</directory>
		<outputDirectory>conf</outputDirectory>
		<fileMode>0644</fileMode>
	</fileSet>
	...
</assembly>

获取属性

AbstractEnvironment中定义的属性ConfigurablePropertyResolver,即PropertySourcesPropertyResolver实例作为访问属性的委托,构造方法里设置了一个PropertySources实例。看下它的getProperty方法:

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
	if (this.propertySources != null) {
		// 遍历所有的PropertySource
		for (PropertySource<?> propertySource : this.propertySources) {
			if (logger.isTraceEnabled()) {
				logger.trace("Searching for key '" + key + "' in PropertySource '" +
						propertySource.getName() + "'");
			}
			Object value = propertySource.getProperty(key);
			// 选用第一个不为null的匹配key的属性值,即如果出现多个PropertySource中存在同名的key,返回的是第一个PropertySource对应key的不为null属性值的处理结果
			if (value != null) {
				if (resolveNestedPlaceholders && value instanceof String) {
					// 处理属性占位符,如${server.port},底层委托到PropertyPlaceholderHelper完成
					value = resolveNestedPlaceholders((String) value);
				}
				logKeyFound(key, propertySource, value);
				// 如果需要的话,进行一次类型转换,底层委托到DefaultConversionService完成
				return convertValueIfNecessary(value, targetValueType);
			}
		}
	}
	if (logger.isTraceEnabled()) {
		logger.trace("Could not find key '" + key + "' in any property source");
	}
	return null;
}

所以总结而言,对于Spring环境属性的管理:从文件中读取数据转化为key-value结构,key-value结构存放在一个PropertySource实例中,然后得到的多个PropertySource实例存放在一个CopyOnWriteArrayList中,属性访问的时候总是遍历CopyOnWriteArrayList中的PropertySource进行匹配,再涉及一些占位符的解析和参数类型的转换

扩展:
Spring 学习记录2 Environment
Spring Environment的加载