1. 程式人生 > >Spring之context:property-placeholder詳解

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;
}

說明:

  1. 如果system-properties-mode=ENVIRONMENT就註冊型別為PropertySourcesPlaceholderConfigurer的Bean,否則為PropertyPlaceholderConfigurer
  2. 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);
	}
}

說明:

  1. 所有屬性放入PropertySources物件中
  2. environment的PropertySource,名稱為environmentProperties
  3. 配置檔案的的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);
}

說明:

  1. ignoreUnresolvablePlaceholders=true,表示找不到不報錯。resolveRequiredPlaceholders方法找不到就拋錯
  2. trimValues=true,會使用trim去除空格
  3. nullValue表示的一個null值的表現
  4. 這個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;
}