何为Envoriment
Envoriment是集成在Spring上下文容器中的核心组件,在Spring源码中由接口抽象。
在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 getProperty(String key, Class 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系统属性 MapgetSystemProperties(); // 获取环境变量 Map 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")// 资源路径中有占位
Environment可以利用properties属性解析这些占位,但是Environment实际委托ConfigurablePropertyResolver解析占位。ConfigurablePropertyResolver可以配置这些占位的前缀和后缀。
Environment可以获取properties,也是委托ConfigurablePropertyResolver解析获取,并同时提供转换服务,可以将property的值转为所需的目标类型,ConfigurablePropertyResolver提供了设置转换服务的接口。
上述中主要介绍Environment体系中定义的接口,接下来分析Environment的抽象实现AbstractEnvironment和标准实现StandardEnvironment。考虑到篇幅原因,本文只介绍Environment的核心功能:
- Environment如何获取jvm系统属性和系统环境变量;
- 如何实现profile决定bean属于哪个profile;
- 设置属性properties原理;
- 如何实现属性获取和资源路径占位解析;
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@Overridepublic 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 MapgetSystemProperties() { 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 MapgetSystemEnvironment() { // 如果设置禁止访问环境变量,将返回空。抑制访问环境变量可以通过 系统属性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中实现:
@Overridepublic 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,可能存在多个 SetcurrentActiveProfiles = doGetActiveProfiles(); // 判断参数profile是否在有效profile中,返回true/false return (currentActiveProfiles.contains(profile) || (currentActiveProfiles.isEmpty() && doGetDefaultProfiles().contains(profile)));}
doGetActiveProfiles主要是惰性Properties中获取有效的profile:
protected SetdoGetActiveProfiles() { // 同步修改,防并发 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:
@Overridepublic String getProperty(String key) { // 属性解析器解析获取spring.profiles.active属性 return this.propertyResolver.getProperty(key);}
@Overridepublic String getProperty(String key) { // 获取属性值并类型转换为String return getProperty(key, String.class);}
以上的getProperty都是AbstractPropertyResolver实现,getProperty(key, String.class)在PropertySourcesPropertyResolver实现:
@OverridepublicT getProperty(String key, Class targetValueType) { return getProperty(key, targetValueType, true);}// resolveNestedPlaceholders是否解析嵌套占位参数protected T getProperty(String key, Class 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{ ...省略 protected final String name; protected final T source; ...省略 public abstract Object getProperty(String name);}
其实现非常多,其中有MapPropertySource以Map作为source,等等。
public class MapPropertySource extends EnumerablePropertySource
在应用中属性源PropertySource来源非常多,有可能是Map对象,也有可能是properies属性文件,所以Spring又在PropertySource上抽象一层多属性源PropertySources,通过其保持多PropertySource:
public interface PropertySources extends Iterable> { // 测试是否包含指定名称属性源 boolean contains(String name); // 通过名称获取属源 PropertySource get(String name);}
其标准实现是易变的多属性源:
public class MutablePropertySources implements PropertySources { // List持有多个PropertySource private final List> propertySourceList = new CopyOnWriteArrayList >(); // 以下都是操作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中接口实现@Overridepublic String getProperty(String key) { // 委托PropertyResolver解析获取 return this.propertyResolver.getProperty(key);}// AbstractPropertyResolver中实现@Overridepublic String getProperty(String key) { // 调用泛型接口实现,由PropertySourcesPropertyResolver实现 // 并指定目标类型 return getProperty(key, String.class);}// PropertySourcesPropertyResolver实现T getProperty(String key, Class targetType)@Overridepublic T getProperty(String key, Class targetValueType) { // 支持属性值中的嵌套占位解析 return getProperty(key, targetValueType, true);}// PropertySourcesPropertyResolver实现,支持优先级检索、嵌套占位解析、值转换protected T getProperty(String key, Class 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获取其中的属性:
以上配置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中该方法实现@Overridepublic String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { // 委托PropertyResolver解析 return this.propertyResolver.resolveRequiredPlaceholders(text);}// 由AbstractPropertyResolver实现@Overridepublic 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 MapwellKnownSimplePrefixes = new HashMap (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());}protected String parseStringValue( String value, PlaceholderResolver placeholderResolver, Set 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
主要是以上两方面的维护操作。