何为Envoriment

Envoriment是集成在Spring上下文容器中的核心组件,在Spring源码中由Envoriment接口抽象。

在Environment中,有两大主要概念:

  • Profile:在Spring中profile是针对Bean定义而言,是Bean定义的逻辑分组。通常表现为:dev/test/production等等,对于Bean定义属于哪个profile是由XML或者Annotation配置决定;
  • Properties:即键值对(Keys-Pairs),在Spring中将*.properties文件/JVM系统属性(JVM System Property)/系统环境变量(JVM Environment Variables)/Properties对象/Map对象抽象为Properties;

Envoriment的能力

Envoriment提供了获取和设置Profile的能力,可以决定生效哪些Profiles。

同时提供了操作Properties的能力,可以增加/移除整个Properties,能获取某个Key-Pair。

  • Environment可以获取系统JVM属性:-Dspring.profiles.active="..."或者显示编程式setActiveProfiles("...")生效相应的profile;
  • 可以通过Environment提供的getActiveProfiles和getDefaultProfiles获取profile;
  • Environment可以决定Bean定义属于哪个Profile;
  • Environment可以操作上下文中的Properties,提供了add/remove接口;
  • Environment集成PropertyResolver,利用Spring中的Conversion服务多Properties中的属性进行类型转化;
  • Environment提供了解析任意Resource资源路径中的${...}占位,支持嵌套占位解析;

上图中体现Environment是上下文ApplicationContext中的核心之一,在实现上每个ApplicationContext中都持有Environment实例:

public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean { ......此处省略 /** Environment used by this context */
private ConfigurableEnvironment environment; ......此处省略 }

从源码分析看Envoriment

Enviroment源码部分主要由三部分组成:

  • Enviroment接口实现
  • PropertyResolver属性解析器接口实现
  • PropertySource和PropertySources属性源

下续UML类图中描述出Environment通过持有PropertyResolver和PropertySources实现对Properties的操作,Environment自身提供profile的存储和接口操作。

Tips:

Spring中大量使用接口继承-实现组合的模式。接口之间继承,具体实现中通过组合持有的方式达到接口隔离,实现强扩展能力。

Environment接口定义:

public interface Environment extends PropertyResolver {

	// 获取当前上下文生效应用的profile
String[] getActiveProfiles(); // 获取当前上下文默认的profile
String[] getDefaultProfiles(); // 判断是否接受某个profile,用于决定bean定义属于哪个profile
boolean acceptsProfiles(String... profiles); }

获取activeProfile和defaultProfile,同时能决定是否接受某个profile。

Environment中继承PropertyResolver接口:

/**
* Interface for resolving properties against any underlying source.
*/
public interface PropertyResolver {
// 判断源中是否包含该key的属性
boolean containsProperty(String key);
// 获取源中的key属性的值
String getProperty(String key);
// 获取并转换成目标类型
<T> T getProperty(String key, Class<T> targetType);
// 解析占位,主要被应用在解析resource路径中的占位、@Value注解中的占位和
Bean定义中的占位
String resolvePlaceholders(String text);
}

PropertyResolver用于解析潜在的源的属性。潜在源可以是多种形式:properties文件、jvm属性、jvm环境变量、map等等。

从以上可以推导出Environment具有从源中解析获取Properties的能力,或者说Environment本身就是属性解析器。

Environment接口体系中非常重要的成员是ConfigurableEnvironment,它提供了更强的处理能力:

  • 设置上下文容器的profile;
  • 操作Environment中properties;
/**
* Configuration interface to be implemented by most if not all {@link Environment} types.
* Provides facilities for setting active and default profiles and manipulating underlying
* property sources. Allows clients to set and validate required properties, customize the
* conversion service and more through the {@link ConfigurablePropertyResolver}
* superinterface.
*/
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver {
// 设置有效的profile
void setActiveProfiles(String... profiles);
// 设置默认的profile
void setDefaultProfiles(String... profiles);
// 获取属性源集合,利用属性源集合可以操作Environment的properties
MutablePropertySources getPropertySources();
// 获取jvm系统属性
Map<String, Object> getSystemProperties();
// 获取环境变量
Map<String, Object> getSystemEnvironment();
// 合并另一个环境,主要用在父子容器中,子容器继承合并父容器的Environment
void merge(ConfigurableEnvironment parent);
}

从javadocs和接口定义上可以看出,该Environment接口主要主要提供配置profile和properties的能力。

ConfigurableEnvironment继承了ConfigurablePropertyResolver接口,该接口是前文中的PropertyResolver的配置实现:

/**
* Configuration interface to be implemented by most if not all {@link PropertyResolver}
* types. Provides facilities for accessing and customizing the
* {@link org.springframework.core.convert.ConversionService ConversionService}
* used when converting property values from one type to another.
*/
public interface ConfigurablePropertyResolver extends PropertyResolver {
// 设置转换服务,转换服务是Spring体系中非常重要的基础组件。用于转换解析出的property类型
void setConversionService(ConfigurableConversionService conversionService);
// 设置占位前缀
void setPlaceholderPrefix(String placeholderPrefix);
// 设置占位后缀
void setPlaceholderSuffix(String placeholderSuffix);
// 设置property中中key-pair分割符
void setValueSeparator(String valueSeparator);
// 设置是否忽略嵌套占位的解析
void setIgnoreUnresolvableNestedPlaceholders(boolean ignoreUnresolvableNestedPlaceholders);
}

前文提及Environment用于解析Resource路径中的占位,如:

// 资源路径中有占位
@PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties")
// 资源路径中有占位
<import resource="classpath:${custome.path}/beans2.xml"></import>

Environment可以利用properties属性解析这些占位,但是Environment实际委托ConfigurablePropertyResolver解析占位。ConfigurablePropertyResolver可以配置这些占位的前缀和后缀。

Environment可以获取properties,也是委托ConfigurablePropertyResolver解析获取,并同时提供转换服务,可以将property的值转为所需的目标类型,ConfigurablePropertyResolver提供了设置转换服务的接口。

上述中主要介绍Environment体系中定义的接口,接下来分析Environment的抽象实现AbstractEnvironment和标准实现StandardEnvironment。考虑到篇幅原因,本文只介绍Environment的核心功能:

  1. Environment如何获取jvm系统属性和系统环境变量;
  2. 如何实现profile决定bean属于哪个profile;
  3. 设置属性properties原理;
  4. 如何实现属性获取和资源路径占位解析;

1.Environment如何获取jvm系统属性和系统环境变量

StandardEnvironment是Spring上下文中标准的Environment,在非web应用中使用该Environment作为ApplicationContext的环境组件:

public class StandardEnvironment extends AbstractEnvironment {

	/** System environment property source name: {@value} */
public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment"; /** JVM system properties property source name: {@value} */
public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties"; @Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
} }

StandardEnvironment实现解析systemEnvironment系统环境变量和systemProperties系统属性作为Properties。在AbstractApplicationContext中实现:

// 获取环境Environment
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
this.environment = createEnvironment();
}
return this.environment;
} // 创建StandardEnvironment实例,加载jvm系统属性和环境变量
protected ConfigurableEnvironment createEnvironment() {
return new StandardEnvironment();
}

在非web环境中使用StandardEnvironment作为标准实现。在web环境中, 有web上下文容器覆盖createEnvironment实现,创建StandardServletEnvironment。

当调用StandardEnvironment构造器时,将调用父类AbstractEnvironment的无参构造器:

public AbstractEnvironment() {
// 自定义属性源,customizePropertySources为抽象方法
// 由子类实现
customizePropertySources(this.propertySources);
if (logger.isDebugEnabled()) {
logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
}
}

StandardEnvironment中关于customizePropertySources实现是为了加载jvm系统属性和环境变量:

@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Object> getSystemProperties() {
try {
// 调用System api获取所有的jvm系统属性
return (Map) System.getProperties();
}
catch (AccessControlException ex) {
// 如果设置了访问权限控制不能访问所有属性,则将返回惰性只读的属性map
return (Map) new ReadOnlySystemAttributesMap() {
@Override
protected String getSystemAttribute(String attributeName) {
try {
// 只获取指定的属性
return System.getProperty(attributeName);
}
catch (AccessControlException ex) {
// 如果仍然无法访问,则返回null,表示没有该属性
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system property '" +
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public Map<String, Object> getSystemEnvironment() {
// 如果设置禁止访问环境变量,将返回空。抑制访问环境变量可以通过
系统属性spring.getenv.ignore设置:true/false,默认是非抑制
if (suppressGetenvAccess()) {
return Collections.emptyMap();
}
try {
// 调用System api获取返回所有的环境变量
return (Map) System.getenv();
}
catch (AccessControlException ex) {
// 同jvm系统属性一样,惰性只读特定的环境变量
return (Map) new ReadOnlySystemAttributesMap() {
@Override
protected String getSystemAttribute(String attributeName) {
try {
return System.getenv(attributeName);
}
catch (AccessControlException ex) {
if (logger.isInfoEnabled()) {
logger.info("Caught AccessControlException when accessing system environment variable '" +
attributeName + "'; its value will be returned [null]. Reason: " + ex.getMessage());
}
return null;
}
}
};
}
}

2.如何实现profile并决定bean属于哪个profile

在Spring中设置profile可以使Spring应用契合多环境:dev/test/pro等等。其中关键点在于:

  • 让Spring应用知道当前处于什么环境,这可以有系统启动时携带参数:系统属性等设置决定,则该环境为Spring应用有效的profile;
  • 当前配置的Bean是属于哪个环境(profile)

Spring容器在解析Bean时会将Bean定义中的profile作为参数传递给Environment,由Environment决定该profile是否可以被当前环境接受。这系列的逻辑由Environment的acceptsProfiles接口承担实现,因为Environment持有当前上下文容器的所有profile(active和default),AbstractEnvironment中实现:

@Override
public boolean acceptsProfiles(String... profiles) {
// 断言输入profile是否为空
Assert.notEmpty(profiles, "Must specify at least one profile");
// 循环遍历每个profile
for (String profile : profiles) {
// bean中profile可以以"!"形式表示非
if (StringUtils.hasLength(profile) && profile.charAt(0) == '!') {
// 该profile不是有效的profile,返回true
if (!isProfileActive(profile.substring(1))) {
return true;
}
}
// 不是以"!"开头,且是当前有效profile,则返回true
else if (isProfileActive(profile)) {
return true;
}
}
return false;
}

再继续阅读isProfileActive实现,该方法重要完成参数的profile是否在当前上下文容器有效的profile中:

protected boolean isProfileActive(String profile) {
// 校验profile合法性
validateProfile(profile);
// 惰性加载当前容器的有效的profile,可能存在多个
Set<String> currentActiveProfiles = doGetActiveProfiles();
// 判断参数profile是否在有效profile中,返回true/false
return (currentActiveProfiles.contains(profile) ||
(currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));
}

doGetActiveProfiles主要是惰性Properties中获取有效的profile:

protected Set<String> doGetActiveProfiles() {
// 同步修改,防并发
synchronized (this.activeProfiles) {
// 如果有效的profiles空,则加载
if (this.activeProfiles.isEmpty()) {
// 解析property,获取有效的profiles
String profiles = getProperty(ACTIVE_PROFILES_PROPERTY_NAME);
if (StringUtils.hasText(profiles)) {
// 设置有效的profile
setActiveProfiles(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(profiles)));
}
}
return this.activeProfiles;
}
}

该方法中核心的调用是getProperty加载当前上下文有效的profile。在Spring中有效的上下文激活方式:

  • 在属性文件中配置属性:spring.profiles.active=dev
  • 在启动时使用jvm参数:-Dspring.profiles.active=dev
  • env.setActiveProfiles("dev")

getProperty方法中获取属性spring.profiles.active的值作为有效的profile:

@Override
public String getProperty(String key) {
// 属性解析器解析获取spring.profiles.active属性
return this.propertyResolver.getProperty(key);
}
@Override
public String getProperty(String key) {
// 获取属性值并类型转换为String
return getProperty(key, String.class);
}

以上的getProperty都是AbstractPropertyResolver实现,getProperty(key, String.class)在PropertySourcesPropertyResolver实现:

@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
return getProperty(key, targetValueType, true);
} // resolveNestedPlaceholders是否解析嵌套占位参数
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
// 判断属性源是否为空
if (this.propertySources != null) {
// 遍历每个属性源
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
// 获取属性源中的key的值
Object value = propertySource.getProperty(key);
// 如果值不为空
if (value != null) {
// 如果支持嵌套解析且值是String类型
if (resolveNestedPlaceholders && value instanceof String) {
// 解析占位
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// 使用转换服务进行转换value为目标类型
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
// 无属性源,返回null
return null;
}

上述流程中,获取到spring.profiles.active属性后,与Bean定义的profile比较,如果在当前有效的profile中,则该bean定义会被注册为spring bean。

3.设置属性properties原理

Environment中的properties分为两种,一种是Spring框架自身加载的:jvm系统属性和系统环境变量——前文中介绍StandardEnvironment;另一种是用户应用自定义的属性加载入Environment。

在Environment中的properties被抽象成:

public abstract class PropertySource<T> {

	...省略

	protected final String name;

	protected final T source;

	...省略

	public abstract Object getProperty(String name);
}

其实现非常多,其中有MapPropertySource以Map作为source,等等。

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
public MapPropertySource(String name, Map<String, Object> source) {
super(name, source);
}
@Override
public Object getProperty(String name) {
return this.source.get(name);
}
@Override
public boolean containsProperty(String name) {
return this.source.containsKey(name);
}
@Override
public String[] getPropertyNames() {
return StringUtils.toStringArray(this.source.keySet());
} }

在应用中属性源PropertySource来源非常多,有可能是Map对象,也有可能是properies属性文件,所以Spring又在PropertySource上抽象一层多属性源PropertySources,通过其保持多PropertySource:

public interface PropertySources extends Iterable<PropertySource<?>> {

	// 测试是否包含指定名称属性源
boolean contains(String name);
// 通过名称获取属源
PropertySource<?> get(String name);
}

其标准实现是易变的多属性源:

public class MutablePropertySources implements PropertySources {

	// List持有多个PropertySource
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<PropertySource<?>>(); // 以下都是操作PropertySource的接口,包括增加移除等操作,体现MutablePropertySources的易变性
public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {
...省略
}
public PropertySource<?> remove(String name) {
...省略
}
public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {
...省略
}
public void addFirst(PropertySource<?> propertySource) {
...省略
}
public void addLast(PropertySource<?> propertySource) {
...省略
}
}

在AbstractEnvironment中持有MutablePropertySources达到Environment中包含properties的目的。

public abstract class AbstractEnvironment implements ConfigurableEnvironment {

	...省略

	// 持有多属性源
private final MutablePropertySources propertySources = new MutablePropertySources(this.logger); // 持有属性解析器,使用上述的propertySources构造,保证解析器的属性源和其实一个
// 属性解析器提供了:按层级搜索属性值、解析获取属性值
private final ConfigurablePropertyResolver propertyResolver =
new PropertySourcesPropertyResolver(this.propertySources); ...省略
}

前文中介绍ConfigurableEnvironment时,改配置环境接口提供了获取多属性源的接口:

MutablePropertySources getPropertySources(),通过该接口获取易变的多属性源可以达到操作Environment中的属性源的操作:addBefore/addFirst/addAfter/AddLaster/remove等等操作属性源的接口。

关于设置Environment中的jvm系统属性和环境变量前文在StandardEnvironment中已经介绍。

4.如何实现属性获取

本文的第一张图中已经画出通过Environment获取属性的流程,Environment通过委托于PropertyResolver完成解析获取属性。PropertyResolver充当的角色:

  • 解析获取属性,并支持类型转换;
  • 按层级搜索属性:即Environment中的PropertySource具有优先级;

关于如何如何解析获取属性的原理在前文的profile原理中已经介绍了部分,这里再细化:

// AbstractEnvironment中接口实现
@Override
public String getProperty(String key) {
// 委托PropertyResolver解析获取
return this.propertyResolver.getProperty(key);
} // AbstractPropertyResolver中实现
@Override
public String getProperty(String key) {
// 调用泛型接口实现,由PropertySourcesPropertyResolver实现
// 并指定目标类型
return getProperty(key, String.class);
} // PropertySourcesPropertyResolver实现<T> T getProperty(String key, Class<T> targetType)
@Override
public <T> T getProperty(String key, Class<T> targetValueType) {
// 支持属性值中的嵌套占位解析
return getProperty(key, targetValueType, true);
} // PropertySourcesPropertyResolver实现,支持优先级检索、嵌套占位解析、值转换
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
// 这里的for循环遍历所有的propertySource解析key,只要获取到value就返回,实现了propertySource的优先级(层级检索)
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
// 实现value值中的嵌套占位解析
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
// 转化value为目标类型,主要使用Spring中的转换服务
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}

Envoriment中易忽略点

前文中一直介绍Environment能够解析获取Spring上下文中的属性源,且支持解析占位${...},但是:

  • Environment是否支持解析Spring中所有的属性源
  • Environment是否支持解析Spring应用出现的任意${...}

1.支持环境相关的属性解析

Environment英文是环境的意思,所以Spring中的Environment组件的属性解析和${...}也只是和环境相关,与环境以外的属性源和${...},Environment是不支持的。

Environment中支持的属性元PropertySource只有以下三种情况:

  • Environment组件自身加载的属性源,如StandardEnvironment中加载的系统属性系统环境变量;如StandardServletEnvironment中加载的ServletContext参数和Servlet参数;
  • 通过ConfigralbeEnvironment获取MutablePropertySources对象,编程式加入的属性源;
  • 通过@PropertySource注解加载的.properties文件中的属性;

javadocs中描述@PropertySource:

Annotation providing a convenient and declarative mechanism for adding a {@link org.springframework.core.env.PropertySource PropertySource} to Spring's {@link org.springframework.core.env.Environment Environment}. To be used in conjunction with @{@link Configuration} classes.

该注解提供便捷声明式的将PropertySource增加的Environment中,该注解需要结合@ Configuration共同使用。如:

@Configuration
@PropertySource("classpath:/com/myco/app.properties")
public class AppConfig { @Autowired
Environment env; @Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
// 通过Environment获取属性
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}

以上的java config可以将类路径上的com/myco/app.properties文件中的属性加载到Environment中,并可以通过Environment获取。

在Spring中还有另外一种加载属性的方式,但是该方式主要是为了处理${...}占位问题,并非Environment中的属性,所以无法通过Environment获取其中的属性:

<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>

以上配置Spring上下文会加载com/foo/jdbc.properties并且实例化PropertySourcesPlaceholderConfigurer用于解析@Value注解中的占位和Xml中Bean定义的占位。在该PropertySourcesPlaceholderConfigurer中也持有MutablePropertySources成员用于存储Properties。所以Spring中的properties不是都能成Environment中获取的。有些与环境无关的properties属于PropertySourcesPlaceholderConfigurer。PropertySourcesPlaceholderConfigurer也实现EnvironmentAware接口,持有Environment组件,所以Spring中的properties在PropertySourcesPlaceholderConfigurer都被包含。

2.支持环境相关的占位解析

Environment中支持的占位${...}解析只与环境相关,前文中介绍只有Resource路径中的占位Environment才负责解析。

Tips:

关于@Value注解和Bean定义的占位,由PropertySourcesPlaceholderConfigurer负责解析,后续文章会详解。

填坑resolvePath

上一篇文章中留下坑点,ClassPathXmlApplicationContext在设置配置文件路径时涉及到配置文件路径的解析问题,暂时搁置到本篇文章中详解。因为资源路径的解析由Environment组件负责路径中的占位解析替换,故需要深入Environment组件后才能更好的理解配置文件路径的解析原理。

回顾上章中的解析点:

public void setConfigLocations(String... locations) {
if (locations != null) {
Assert.noNullElements(locations, "Config locations must not be null");
this.configLocations = new String[locations.length];
for (int i = 0; i < locations.length; i++) {
// 解析配置文件路径
this.configLocations[i] = resolvePath(locations[i]).trim();
}
}
else {
this.configLocations = null;
}
}

解析配置文件路径的逻辑由resolvePath完成,主要是解析资源配置文件中的默认占位${...}或者自定义占位:

protected String resolvePath(String path) {
// 获取该ApplicationContext上下文对应的Environment
// 委托Environment解析配置文件路径
return getEnvironment().resolveRequiredPlaceholders(path);
}

这里重点看Environment解析的细节:

// 由AbstractEnvironment中该方法实现
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
// 委托PropertyResolver解析
return this.propertyResolver.resolveRequiredPlaceholders(text);
} // 由AbstractPropertyResolver实现
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
// 解析需要工具类PropertyPlaceholderHelper,如果无,则创建
if (this.strictHelper == null) {
this.strictHelper = createPlaceholderHelper(false);
}
// 解析处理
return doResolvePlaceholders(text, this.strictHelper);
}

再进一不深入解析处理doResolvePlaceholders逻辑前,先看下PropertyPlaceholderHelper工具类细节:

public class PropertyPlaceholderHelper {
private static final Map<String, String> wellKnownSimplePrefixes = new HashMap<String, String>(4);
static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}
// 占位符前缀
private final String placeholderPrefix;
// 占位符后缀
private final String placeholderSuffix;
private final String simplePrefix;
// key-pair分隔符
private final String valueSeparator;
// 忽略解析占位的flag
private final boolean ignoreUnresolvablePlaceholders;
}

工具类主要是帮助解析String中的占位符,需要匹配String中占位符前缀位置、后缀位置,然后截取占位符中的内容,再将内容作为PropertyResolver参数,从PropertySoures中按优先级检索属性值,再将属性值替换占位,即完成配置文件完整路径的解析。

createPlaceholderHelper方法中主要是创建工具类,指定工具类的占位前后缀:

public static final String PLACEHOLDER_PREFIX = "${";
public static final String PLACEHOLDER_SUFFIX = "}"; private String placeholderPrefix = SystemPropertyUtils.PLACEHOLDER_PREFIX;
private String placeholderSuffix = SystemPropertyUtils.PLACEHOLDER_SUFFIX; private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
this.valueSeparator, ignoreUnresolvablePlaceholders);
}

从以上可以看出Spring默认的占位符是${}。

// AbstractPropertyResolver中实现
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
// 工具类通过回调的方式完成解析,实现匿名内部类
return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
@Override
public String resolvePlaceholder(String placeholderName) {
// helper负责获取占位符中的内容
// AbstractPropertyResolver将占位符中内容作为key,解析对应的属性值
return getPropertyAsRawString(placeholderName);
}
});
}
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
// 解析占位属性值
return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
// 获取占位符前缀位置
int startIndex = value.indexOf(this.placeholderPrefix);
如果存在占位,则解析,否则返回
while (startIndex != -1) {
// 获取占位符后缀位置
int endIndex = findPlaceholderEndIndex(result, startIndex);
// 如果存在,则继续解析,否则设置startIndex为-1,使while退出
if (endIndex != -1) {
// 根据前缀和后缀位置获取占位符中的内容
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
// 判断是否有循环嵌套,这里不支持循环嵌套占位
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// 递归解析嵌套占位
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// 从propertySources中解析占位内容对应的实际属性值
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// 如果属性值不为空,则需要递归解析属性值中是否也有嵌套占位
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
// 将获取的属性值替换占位
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
// 处理完一个占位,移除
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
// 返回完整的解析结果
return result.toString();
}

关于getPropertyAsRawString从PropertySources中解析占位内容对应的属性值,这里不在详细介绍,逻辑与前文中的profile和属性解析流程一致。

通过Environment提供的Resource路径占位解析能力,从而可以得到完整的配置文件路径,Spring上下文可以根据配置文件路径,解析配置中的Bean配置,从而完成后续的Bean解析等等工作。

总结

本文介绍Spring上下文容器的核心组件Environment的能力和实现细节。Environment主要提供能力:

  • profile
  • Properties

主要是以上两方面的维护操作。

参考

Environment abstraction

Spring 中无处不在的 Properties

Properties with Spring

Spring源码系列 — Envoriment组件的更多相关文章

  1. Spring源码系列 — 注解原理

    前言 前文中主要介绍了Spring中处理BeanDefinition的扩展点,其中着重介绍BeanDefinitionParser方式的扩展.本篇文章承接该内容,详解Spring中如何利用BeanDe ...

  2. Spring源码系列(二)--bean组件的源码分析

    简介 spring-bean 组件是 Spring IoC 的核心,我们可以使用它的 beanFactory 来获取所需的对象,对象的实例化.属性装配和初始化等都可以交给 spring 来管理. 本文 ...

  3. Spring源码系列(三)--spring-aop的基础组件、架构和使用

    简介 前面已经讲完 spring-bean( 详见Spring ),这篇博客开始攻克 Spring 的另一个重要模块--spring-aop. spring-aop 可以实现动态代理(底层是使用 JD ...

  4. Spring源码系列 — BeanDefinition

    一.前言 回顾 在Spring源码系列第二篇中介绍了Environment组件,后续又介绍Spring中Resource的抽象,但是对于上下文的启动过程详解并未继续.经过一个星期的准备,梳理了Spri ...

  5. Ioc容器beanDefinition-Spring 源码系列(1)

    Ioc容器beanDefinition-Spring 源码系列(1) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器 ...

  6. Spring源码系列 — BeanDefinition扩展点

    前言 前文介绍了Spring Bean的生命周期,也算是XML IOC系列的完结.但是Spring的博大精深,还有很多盲点需要摸索.整合前面的系列文章,从Resource到BeanDefinition ...

  7. Spring源码系列 — Bean生命周期

    前言 上篇文章中介绍了Spring容器的扩展点,这个是在Bean的创建过程之前执行的逻辑.承接扩展点之后,就是Spring容器的另一个核心:Bean的生命周期过程.这个生命周期过程大致经历了一下的几个 ...

  8. Spring源码系列(四)--spring-aop是如何设计的

    简介 spring-aop 用于生成动态代理类(底层是使用 JDK 动态代理或 cglib 来生成代理类),搭配 spring-bean 一起使用,可以使 AOP 更加解耦.方便.在实际项目中,spr ...

  9. 事件机制-Spring 源码系列(4)

    事件机制-Spring 源码系列(4) 目录: Ioc容器beanDefinition-Spring 源码(1) Ioc容器依赖注入-Spring 源码(2) Ioc容器BeanPostProcess ...

随机推荐

  1. java高并发系列 - 第11天:线程中断的几种方式

    java高并发系列第11篇文章. 本文主要探讨一下中断线程的几种方式. 通过一个变量控制线程中断 代码: package com.itsoku.chat05; import java.util.con ...

  2. .net 定时任务调度

    前段时间开发个项目需要自定义时间定时发送邮件,此处使用了Quartz 定时任务,在此记录下: /// <summary> /// 创建定时任务 /// </summary> / ...

  3. jquery中的ajax请求到php(学生笔记)

    首先ajax的基本语法基础.(必须得引入一个jquery文件,下面的例子展示用了网上的jquery文件,要联网.) 2.请求成功(复制代码运行观察效果) <!DOCTYPE html> & ...

  4. curl_multi_*模拟多线程异步用法

    测试环境: PHP版本:php7.0.10 mysql版本:5.7.14 测试用例:循环插入两千行数据到数据库 public function test_syn($pc){ // $pc = trim ...

  5. flex布局开发

    flex布局开发 布局原理 flex时flexible Box的缩写,意为"弹性布局",用来为盒子模型提供最大的灵活性,任何一个容器都可以定位flex布局 [注意] 当我们为父盒子 ...

  6. Dynamics 365中计算字段与Now进行计算实体导入报错:You can't use Now(), which is of type DateTime, with the current function.

    微软动态CRM专家罗勇 ,回复338或者20190521可方便获取本文,同时可以在第一间得到我发布的最新博文信息,follow me. 计算字段是从Dynamics CRM 2015 SP1版本开始推 ...

  7. Xcode 7.3 解决自定义类无法自动联想

    正在苦拼的码友们,最近是不是觉得在写代码的时候很是头疼,甚至连个最基本的系统自带的语法啊.单词啊等等都不能联想了,之前习惯了的码友们,这个时候肯定会觉得是不是自己写错了,然后也往下翻了一大篇,还是找不 ...

  8. linux远程桌面连接 VNC Server

    更新源 # sudo apt-get update 安装vnc4server # sudo apt-get install vnc4server 修改vnc远程连接密码 # vncpasswd 编辑v ...

  9. iOS自定义TabBar使用popToRootViewControllerAnimated返回后tabbar重叠

    解决方法 所以方法就是:遵循UINavigationController的代理,用代理方法解决该Bug,代码如下: 实现代理方法: { // 删除系统自带的tabBarButton for (UIVi ...

  10. bayaim_杀神_全民飞机大战

    bayaim_杀神_全民飞机大战 ------------------------ 系统:IOS QQ:7742469490 王者:30 游戏名:神 级别:98 装备:满鸡 + 新战魂+ 歼31 + ...