概述:
在上篇的《spring的启动过程03-工厂后置处理器》文章中讲解了工厂后置处理器的原理,这篇文章将会结合具体的功能详细讲解占位符的替换过程。
spring的实际使用过程中会有两个地方用到占位符替代属性值
第一种方式:xml中注入属性值
<!-- 数据库连接池 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" p:driverClassName="${jdbc.driver}" p:url="${jdbc.url}"
p:username="${jdbc.username}" p:password="${jdbc.password}"
p:initialSize="${dataSource.initialSize}" p:minIdle="${dataSource.minIdle}"
p:maxIdle="${dataSource.maxIdle}" p:maxWait="${dataSource.maxWait}"
p:maxActive="${dataSource.maxActive}" p:logAbandoned="${dataSource.logAbandoned}"
p:removeAbandoned="${dataSource.removeAbandoned}"
p:removeAbandonedTimeout="${dataSource.removeAbandonedTimeout}" />
第二种方式:采用value标签方式
@Value(value = "${alias}")
private String alias;
@Value(value = "${password}")
private String password;
spring处理以上两种占位符的替换采用不同的方式,xml注入的占位符spring采用bean工厂后置处理器处理,注解方式的占位符spring采用bean后置处理器处理,这种方式
将会在后面的系列中讲解。
原理:
spring框架实现资源加载有两种方式:
1.采用schema方式
<context:property-placeholder location="classpath:conf/container.properties" />
引入该配置后,spring会创建PropertySourcesPlaceholderConfigurer实体bean,该bean为工厂后置处理器会加载指定的文件并替换beanDefinition对象里面的占位符。
2.采用bean方式
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:conf/*.properties</value>
</list>
</property>
</bean>
先看下类org.springframework.beans.factory.config.PropertyPlaceholderConfigurer静态类图:
spring启动过程中的触发点
/**
* {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
* {@linkplain #processProperties process} properties against the given bean factory.
* @throws BeanInitializationException if any properties cannot be loaded
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
Properties mergedProps = mergeProperties();加载properties文件,会触发如下逻辑:
/**
* Load properties into the given instance.
* @param props the Properties instance to load into
* @throws IOException in case of I/O errors
* @see #setLocations
*/
protected void loadProperties(Properties props) throws IOException {
if (this.locations != null) {
for (Resource location : this.locations) {
PropertiesLoaderUtils.fillProperties(
props, new EncodedResource(location, this.fileEncoding), this.propertiesPersister);
}
}
}
processProperties(beanFactory, mergedProps);会触发如下逻辑:
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
//从bean工厂中获取所有beanDefinition集合
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
//核心逻辑,处理BeanDefinition的属性占位符
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
继续看核心代码:
/**
* Traverse the given BeanDefinition object and the MutablePropertyValues
* and ConstructorArgumentValues contained in them.
* @param beanDefinition the BeanDefinition object to traverse
* @see #resolveStringValue(String)
*/
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition); //替换parentName属性
visitBeanClassName(beanDefinition);//替换beanClassName属性
visitFactoryBeanName(beanDefinition);//替换factoryBeanName属性
visitFactoryMethodName(beanDefinition);//替换factoryMethodName属性
visitScope(beanDefinition);//替换scope属性
visitPropertyValues(beanDefinition.getPropertyValues());//替换易变的属性列表
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
以替换易变的属性列表进行跟踪
protected void visitPropertyValues(MutablePropertyValues pvs) {
PropertyValue[] pvArray = pvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
pvs.add(pv.getName(), newVal);
}
}
}
spring会根据参数值的类型进行不同的处理:
@SuppressWarnings("rawtypes")
protected Object resolveValue(Object value) {
if (value instanceof BeanDefinition) {
visitBeanDefinition((BeanDefinition) value);
}
else if (value instanceof BeanDefinitionHolder) {
visitBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
}
else if (value instanceof RuntimeBeanReference) {
RuntimeBeanReference ref = (RuntimeBeanReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanReference(newBeanName);
}
}
else if (value instanceof RuntimeBeanNameReference) {
RuntimeBeanNameReference ref = (RuntimeBeanNameReference) value;
String newBeanName = resolveStringValue(ref.getBeanName());
if (!newBeanName.equals(ref.getBeanName())) {
return new RuntimeBeanNameReference(newBeanName);
}
}
else if (value instanceof Object[]) {
visitArray((Object[]) value);
}
else if (value instanceof List) {
visitList((List) value);
}
else if (value instanceof Set) {
visitSet((Set) value);
}
else if (value instanceof Map) {
visitMap((Map) value);
}
else if (value instanceof TypedStringValue) {
TypedStringValue typedStringValue = (TypedStringValue) value;
String stringValue = typedStringValue.getValue();
if (stringValue != null) {
String visitedString = resolveStringValue(stringValue);
typedStringValue.setValue(visitedString);
}
}
else if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}
这里我们重点看下字符串类型的处理逻辑
/**
* Resolve the given String value, for example parsing placeholders.
* @param strVal the original String value
* @return the resolved String value
*/
protected String resolveStringValue(String strVal) {
if (this.valueResolver == null) {
throw new IllegalStateException("No StringValueResolver specified - pass a resolver " +
"object into the constructor or override the 'resolveStringValue' method");
}
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
这里会调用PropertyPlaceholderHelper类的parseStringValue方法获取占位符对应的值,跟了这么深才找到重点,继续看
protected String parseStringValue(
String strVal, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
StringBuilder result = new StringBuilder(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
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");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
// 这里会获取占位符对应的值
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) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
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 string value \"" + strVal + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
}
else {
startIndex = -1;
}
}
继续找重点,spring就是这么深,各种句柄各种子类
/**
* Resolve the given placeholder using the given properties, performing
* a system properties check according to the given mode.
* <p>The default implementation delegates to {@code resolvePlaceholder
* (placeholder, props)} before/after the system properties check.
* <p>Subclasses can override this for custom resolution strategies,
* including customized points for the system properties check.
* @param placeholder the placeholder to resolve
* @param props the merged properties of this configurer
* @param systemPropertiesMode the system properties mode,
* according to the constants in this class
* @return the resolved value, of null if none
* @see #setSystemPropertiesMode
* @see System#getProperty
* @see #resolvePlaceholder(String, java.util.Properties)
*/
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
//获取 VM参数 System.getProperty(key);获取属性值
//获取系统环境变量 System.getenv(key);
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
//采用locations加载的properties值
propVal = resolvePlaceholder(placeholder, props);
}
//这里主要是Mode值不同采取获取值的优先级不同,系统默认为先获取locations值
//为空再从VM参数或者系统参数中查找
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
哎,贴了这么多代码,终于见到真主了,好累。。。,我们分析下上面的方法
/**
* Resolve the given key as JVM system property, and optionally also as
* system environment variable if no matching system property has been found.
* @param key the placeholder to resolve as system property key
* @return the system property value, or {@code null} if not found
* @see #setSearchSystemEnvironment
* @see System#getProperty(String)
* @see System#getenv(String)
*/
protected String resolveSystemProperty(String key) {
try {
String value = System.getProperty(key);
if (value == null && this.searchSystemEnvironment) {
value = System.getenv(key);
}
return value;
}
catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not access system property '" + key + "': " + ex);
}
return null;
}
}
/**
* Resolve the given placeholder using the given properties.
* The default implementation simply checks for a corresponding property key.
* <p>Subclasses can override this for customized placeholder-to-key mappings
* or custom resolution strategies, possibly just using the given properties
* as fallback.
* <p>Note that system properties will still be checked before respectively
* after this method is invoked, according to the system properties mode.
* @param placeholder the placeholder to resolve
* @param props the merged properties of this configurer
* @return the resolved value, of {@code null} if none
* @see #setSystemPropertiesMode
*/
protected String resolvePlaceholder(String placeholder, Properties props) {
return props.getProperty(placeholder);
}
看到这里一目了然,spring如何取参数覆盖占位符的过程从代码层面我们已经跟踪完毕。
总结:
下篇文章将会介绍spring的另一个概念“bean后置处理器”,spring替换value注解的占位符方式就是采用了bean后置处理器功能。