一、結構類圖
①、PropertyResolver : Environment的頂層接口,主要提供屬性檢索和解析帶占位符的文本。bean.xml配置中的所有占位符例如${}都由它解析
②、ConfigurablePropertyResolver : 該接口定義了如何對組件本身進行配置。如:剛剛提到獲取value時可以指定任意類型,這依賴於ConversionService進行類型轉換,當前接口就提供了對ConversionService的設置和獲取。另外,可以配置屬性占位符的格式,包括:占位符前綴(默認為"${")、占位符后綴(默認為"}")、占位符值分隔符(默認為":",用於分隔propertyName和defaultValue)。組件還可以設置哪些屬性是必須存在的,還可以校驗必須存在的屬性是否真的存在(不存在的話會拋出異常)
③、AbstractPropertyResolver : 實現了ConfigurablePropertyResolver接口的所有方法
④、PropertySourcesPropertyResolver : 以PropertySources屬性源集合(內部持有屬性源列表List<PropertySource>)為屬性值的來源,按序遍歷每個PropertySource,獲取到一個非null的屬性值則返回
二、demo示例

public static void main(String[] args) { Properties properties = System.getProperties(); properties.setProperty("prefixName", "read-code"); ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:${prefixName}-spring.xml"); ReadCodeService readCodeService = (ReadCodeService) ac.getBean("readCodeService"); readCodeService.say(); }
三、源碼剖析
1、入口 :
ClassPathXmlApplicationContext 構造函數setConfigLocations
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
2、AbstractRefreshableConfigApplicationContext
①、ClassPathXmlApplicationContext構造函數調用它的基類AbstractRefreshableConfigApplicationContext.setConfigLocations
/** * Set the config locations for this application context. * <p>If not set, the implementation may use a default as appropriate. */ 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; } }
②、解析路勁
/** * Resolve the given path, replacing placeholders with corresponding * environment property values if necessary. Applied to config locations. * @param path the original file path * @return the resolved file path * @see org.springframework.core.env.Environment#resolveRequiredPlaceholders(String) */ protected String resolvePath(String path) { return getEnvironment().resolveRequiredPlaceholders(path); }
3、AbstractPropertyResolver
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException { if (this.strictHelper == null) { this.strictHelper = createPlaceholderHelper(false); } return doResolvePlaceholders(text, this.strictHelper); }
上述方法主要做了兩件事 :
①、初始化占位符解析器
createPlaceholderHelper : 主要是初始化占位符的常量,eg : 前綴 ${ 后綴} and so on
②、調用私有方法---替換占位符具體值
private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) { return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() { @Override public String resolvePlaceholder(String placeholderName) { return getPropertyAsRawString(placeholderName); } }); }
4、占位符 key - > value ,
實現PropertyPlaceholderHelper內部接口PlaceholderResolver方法resolvePlaceholder。找到占位符key對應的value,為下文替換key埋下伏筆
protected String getPropertyAsRawString(String key) { return getProperty(key, String.class, false); }
代碼太多了,這里只給出重點

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { boolean debugEnabled = logger.isDebugEnabled(); if (logger.isTraceEnabled()) { logger.trace(String.format("getProperty(\"%s\", %s)", key, targetValueType.getSimpleName())); } if (this.propertySources != null) { for (PropertySource<?> propertySource : this.propertySources) { if (debugEnabled) { logger.debug(String.format("Searching for key '%s' in [%s]", key, propertySource.getName())); } Object value; if ((value = propertySource.getProperty(key)) != null) { Class<?> valueType = value.getClass(); if (resolveNestedPlaceholders && value instanceof String) { value = resolveNestedPlaceholders((String) value); } if (debugEnabled) { logger.debug(String.format("Found key '%s' in [%s] with type [%s] and value '%s'", key, propertySource.getName(), valueType.getSimpleName(), value)); } if (!this.conversionService.canConvert(valueType, targetValueType)) { throw new IllegalArgumentException(String.format( "Cannot convert value [%s] from source type [%s] to target type [%s]", value, valueType.getSimpleName(), targetValueType.getSimpleName())); } return this.conversionService.convert(value, targetValueType); } } } if (debugEnabled) { logger.debug(String.format("Could not find key '%s' in any property source. Returning [null]", key)); } return null; }
5、占位符解析器, 解析並替換具體值得邏輯在這里
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 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); // 這里是調用第四步驟的實現PropertyPlaceholderHelper內部接口PlaceholderResolver方法resolvePlaceholder :占位符 key -> value 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; } } return result.toString(); }
findPlaceholderEndIndex 查找占位符在所在字符串后綴的位置

private int findPlaceholderEndIndex(CharSequence buf, int startIndex) { int index = startIndex + this.placeholderPrefix.length(); int withinNestedPlaceholder = 0; while (index < buf.length()) { if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) { if (withinNestedPlaceholder > 0) { withinNestedPlaceholder--; index = index + this.placeholderSuffix.length(); } else { return index; } } else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) { withinNestedPlaceholder++; index = index + this.simplePrefix.length(); } else { index++; } } return -1; }
StringUtis.substringMatch 匹配當前位置的字符是否為占位符后綴

public static boolean substringMatch(CharSequence str, int index, CharSequence substring) { for (int j = 0; j < substring.length(); j++) { int i = index + j; if (i >= str.length() || str.charAt(i) != substring.charAt(j)) { return false; } } return true; }