Spring之context:property-placeholder詳解
概述
<context:property-placeholder>的作用是向Spring容器中注入一個屬性佔位解析器,用來處理BeanDefinition中的各種佔位符,用配置檔案的資訊替換佔位符。這是個自定義標籤,Spring會使用PropertyPlaceholderBeanDefinitionParser解析它。
這個標籤可以定義各種屬性
- location:屬性檔案路徑,符合Spring的Resource定義
- properties-ref:指定Properties物件的bean
- file-encoding
- ignore-resource-not-found:預設false,表示屬性檔案找不到就報錯
- ignore-unresolvable:預設false,表示屬性找不到就報錯
- local-override:預設false,如果localOverride=false,配置的優先順序:environment > location > property-ref,如果localOverride=true,配置的優先順序:environment < location < property-ref
- system-properties-mode:ENVIRONMENT、NEVER、FALLBACK、OVERRIDE,預設是ENVIRONMENT
- value-separator,間隔符,預設 ':'
- trim-values,預設false,true表示解析佔位符的時候,執行trim操作去除兩端的空格
- null-value,定義一個null值判斷,符合這個值的就表示應該返回null
PS:localOverride的運用,如果localOverride=false,配置的優先順序:environment > location > property-ref,如果localOverride=true,配置的優先順序:environment < location < property-ref
我從原始碼進行分析,仔細看下這些屬性是怎麼運用的。
<context:property-placeholder>的解析
這是個自定義標籤,Spring會使用PropertyPlaceholderBeanDefinitionParser解析它。
@Override
protected Class<?> getBeanClass(Element element) {
// system-properties-mode=ENVIRONMENT
if (element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIB).equals(SYSTEM_PROPERTIES_MODE_DEFAULT)) {
return PropertySourcesPlaceholderConfigurer.class;
}
return PropertyPlaceholderConfigurer.class;
}
說明:
- 如果system-properties-mode=ENVIRONMENT就註冊型別為PropertySourcesPlaceholderConfigurer的Bean,否則為PropertyPlaceholderConfigurer
- PropertySourcesPlaceholderConfigurer與PropertyPlaceholderConfigurer相比,就是增加environment中的屬性
PropertySourcesPlaceholderConfigurer
將所有的屬性,包括environment、location、properties-ref,組裝到PropertySources物件中。然後使用PropertySources去獲取需要替換屬性的真實值,依次迭代PropertySource,返回第一個找到的值。PropertySources是PropertySource的資料結構,裡面封裝多個PropertySource物件。
解析佔位符過程
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.propertySources == null) {
// 1、準備PropertySources屬性
...
}
// 2、解析佔位符
processProperties(beanFactory, new PropertySourcesPropertyResolver(this.propertySources));
this.appliedPropertySources = this.propertySources;
}
1、準備PropertySources屬性
if (this.propertySources == null) {
this.propertySources = new MutablePropertySources();
if (this.environment != null) {
// 加入environment的PropertySource,名稱為environmentProperties
this.propertySources.addLast(
new PropertySource<Environment>(ENVIRONMENT_PROPERTIES_PROPERTY_SOURCE_NAME, this.environment) {
@Override
public String getProperty(String key) {
// 從environment中獲取屬性
return this.source.getProperty(key);
}
}
);
}
try {
// 加入配置檔案的的PropertySource,名稱為localProperties
PropertySource<?> localPropertySource =
new PropertiesPropertySource(LOCAL_PROPERTIES_PROPERTY_SOURCE_NAME, mergeProperties());
// 如果localOverride=true,就優先使用localProperties裡的屬性。做法是把localProperties的屬性放在environmentProperties的前面
if (this.localOverride) {
this.propertySources.addFirst(localPropertySource);
}
else {
this.propertySources.addLast(localPropertySource);
}
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
說明:
- 所有屬性放入PropertySources物件中
- environment的PropertySource,名稱為environmentProperties
- 配置檔案的的PropertySource,名稱為localProperties
PS:localOverride的運用,如果localOverride=false,配置的優先順序:environment > location > property-ref,如果localOverride=true,配置的優先順序:environment < location < property-ref
1.1、mergeProperties()
protected Properties mergeProperties() throws IOException {
Properties result = new Properties();
if (this.localOverride) {
// 先載入location定義的檔案,會被property-ref中的屬性覆蓋
// Load properties from file upfront, to let local properties override.
loadProperties(result);
}
if (this.localProperties != null) {
// 載入bean中定義的property物件
for (Properties localProp : this.localProperties) {
CollectionUtils.mergePropertiesIntoMap(localProp, result);
}
}
if (!this.localOverride) {
// 後加載location定義的檔案,會覆蓋property-ref中的屬性
// Load properties from file afterwards, to let those properties override.
loadProperties(result);
}
return result;
}
2、解析佔位符
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
final ConfigurablePropertyResolver propertyResolver) throws BeansException {
propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
propertyResolver.setValueSeparator(this.valueSeparator);
StringValueResolver valueResolver = strVal -> {
String resolved = (ignoreUnresolvablePlaceholders ?
propertyResolver.resolvePlaceholders(strVal) :
propertyResolver.resolveRequiredPlaceholders(strVal));
if (trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
};
doProcessProperties(beanFactoryToProcess, valueResolver);
}
說明:
- ignoreUnresolvablePlaceholders=true,表示找不到不報錯。resolveRequiredPlaceholders方法找不到就拋錯
- trimValues=true,會使用trim去除空格
- nullValue表示的一個null值的表現
- 這個propertyResolver是上個方法建立的PropertySourcesPropertyResolver物件
3、PropertySourcesPropertyResolver
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() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
// 先找到先返回
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isDebugEnabled()) {
logger.debug("Could not find key '" + key + "' in any property source");
}
return null;
}