概述:
在上篇的《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後置處理器功能。