提示:由于作者水平和时间有限,请仅以参考的态度阅读。
在使用SpringMVC做Web开发的时候,为了便于统一管理配置项,常常会看到用占位符的配置方式。这样,可以将分散在spring配置文件中的配置项的值集中到一个(多个)属性文件中,方便管理。
比如定义了一个bean,属性的值使用占位符,如下(applicationContext.xml)
虽然在3.1开始官方文档推荐优先使用PropertySourcesPlaceholderConfigurer取代PropertyPlaceholderConfigurer,但是研究分析下PropertyPlaceholderConfigurer还是会有不少收获。spring占位符替换主要涉及到BeanFactoryPostProcessor接口和PropertyPlaceholderConfigurer、PlaceholderConfigurerSupport、PropertyResourceConfigurer三个类。
Spring提供了的一种叫做BeanFactoryPostProcessor的容器扩展机制。它允许我们在容器实例化对象之前,对容器中的BeanDefinition中的信息做一定的修改(比如对某些字段的值进行修改,这就是占位符替换的根本)。于是就需要说下BeanFactoryPostProcessor接口了,以下BeanFactoryPostProcessor的定义
看看PropertyPlaceholderConfigurer、PlaceholderConfigurerSupport、PropertyResourceConfigurer三个类的定义
先看看PropertyResourceConfigurer的postProcessBeanFactory()方法
接着看PropertyPlaceholderConfigurer的processProperties()方法
再看PlaceholderConfigurerSupport的doProcessProperties()方法
在了解了PropertyPlaceholderConfigurer做占位符替换的流程之后,我们应该有所启发:
1、要定制自己的占位符替换实现,入口就在BeanFactoryPostProcessor接口。实现BeanFactoryPostProcessor接口,并替换掉PropertyPlaceholderConfigurer即可。
2、占位符替换过程中,最主要的是Properties,整合出自己的Properties后,spring现成的很多代码可以继续使用。
目前我们采用Zookeeper做配置中心,用于管理多个APP实例上的配置,而基本的思路就是实现BeanFactoryPostProcessor接口,从Zookeeper上取相应节点,构造出Properties。
附上PropertyPlaceholderConfigurer的源码,可以基于该类的实现自己的 占位符替换功能(properties或缓存中)
/*
* Copyright 2002-2008 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.beans.factory.config;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.core.Constants;
import org.springframework.util.StringUtils;
import org.springframework.util.StringValueResolver;
/**
* A property resource configurer that resolves placeholders in bean property values of
* context definitions. It pulls values from a properties file into bean definitions.
*
* The default placeholder syntax follows the Ant / Log4J / JSP EL style:
*
*
${...}
*
* Example XML context definition:
*
*
* ${driver}
* jdbc:${dbname}
*
*
* Example properties file:
*
* driver=com.mysql.jdbc.Driver
* dbname=mysql:mydb
*
* PropertyPlaceholderConfigurer checks simple property values, lists, maps,
* props, and bean names in bean references. Furthermore, placeholder values can
* also cross-reference other placeholders, like:
*
* rootPath=myrootdir
* subPath=${rootPath}/subdir
*
* In contrast to PropertyOverrideConfigurer, this configurer allows to fill in
* explicit placeholders in context definitions. Therefore, the original definition
* cannot specify any default values for such bean properties, and the placeholder
* properties file is supposed to contain an entry for each defined placeholder.
*
* If a configurer cannot resolve a placeholder, a BeanDefinitionStoreException
* will be thrown. If you want to check against multiple properties files, specify
* multiple resources via the "locations" setting. You can also define multiple
* PropertyPlaceholderConfigurers, each with its own placeholder syntax.
*
*
Default property values can be defined via "properties", to make overriding
* definitions in properties files optional. A configurer will also check against
* system properties (e.g. "user.dir") if it cannot resolve a placeholder with any
* of the specified properties. This can be customized via "systemPropertiesMode".
*
*
Note that the context definition is aware of being incomplete;
* this is immediately obvious to users when looking at the XML definition file.
* Hence, placeholders have to be resolved; any desired defaults have to be
* defined as placeholder values as well (for example in a default properties file).
*
*
Property values can be converted after reading them in, through overriding
* the {@link #convertPropertyValue} method. For example, encrypted values can
* be detected and decrypted accordingly before processing them.
*
* @author Juergen Hoeller
* @since 02.10.2003
* @see #setLocations
* @see #setProperties
* @see #setPlaceholderPrefix
* @see #setPlaceholderSuffix
* @see #setSystemPropertiesModeName
* @see System#getProperty(String)
* @see #convertPropertyValue
* @see PropertyOverrideConfigurer
*/
public class PropertyPlaceholderConfigurer extends PropertyResourceConfigurer
implements BeanNameAware, BeanFactoryAware {
/** Default placeholder prefix: "${" */
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
/** Default placeholder suffix: "}" */
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
/** Never check system properties. */
public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
/**
* Check system properties if not resolvable in the specified properties.
* This is the default.
*/
public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
/**
* Check system properties first, before trying the specified properties.
* This allows system properties to override any other property source.
*/
public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class);
private String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
private String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
private boolean searchSystemEnvironment = true;
private boolean ignoreUnresolvablePlaceholders = false;
private String nullValue;
private String beanName;
private BeanFactory beanFactory;
/**
* Set the prefix that a placeholder string starts with.
* The default is "${".
* @see #DEFAULT_PLACEHOLDER_PREFIX
*/
public void setPlaceholderPrefix(String placeholderPrefix) {
this.placeholderPrefix = placeholderPrefix;
}
/**
* Set the suffix that a placeholder string ends with.
* The default is "}".
* @see #DEFAULT_PLACEHOLDER_SUFFIX
*/
public void setPlaceholderSuffix(String placeholderSuffix) {
this.placeholderSuffix = placeholderSuffix;
}
/**
* Set the system property mode by the name of the corresponding constant,
* e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
* @param constantName name of the constant
* @throws java.lang.IllegalArgumentException if an invalid constant was specified
* @see #setSystemPropertiesMode
*/
public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException {
this.systemPropertiesMode = constants.asNumber(constantName).intValue();
}
/**
* Set how to check system properties: as fallback, as override, or never.
* For example, will resolve ${user.dir} to the "user.dir" system property.
*
The default is "fallback": If not being able to resolve a placeholder
* with the specified properties, a system property will be tried.
* "override" will check for a system property first, before trying the
* specified properties. "never" will not check system properties at all.
* @see #SYSTEM_PROPERTIES_MODE_NEVER
* @see #SYSTEM_PROPERTIES_MODE_FALLBACK
* @see #SYSTEM_PROPERTIES_MODE_OVERRIDE
* @see #setSystemPropertiesModeName
*/
public void setSystemPropertiesMode(int systemPropertiesMode) {
this.systemPropertiesMode = systemPropertiesMode;
}
/**
* Set whether to search for a matching system environment variable
* if no matching system property has been found. Only applied when
* "systemPropertyMode" is active (i.e. "fallback" or "override"), right
* after checking JVM system properties.
*
Default is "true". Switch this setting off to never resolve placeholders
* against system environment variables. Note that it is generally recommended
* to pass external values in as JVM system properties: This can easily be
* achieved in a startup script, even for existing environment variables.
*
NOTE: Access to environment variables does not work on the
* Sun VM 1.4, where the corresponding {@link System#getenv} support was
* disabled - before it eventually got re-enabled for the Sun VM 1.5.
* Please upgrade to 1.5 (or higher) if you intend to rely on the
* environment variable support.
* @see #setSystemPropertiesMode
* @see java.lang.System#getProperty(String)
* @see java.lang.System#getenv(String)
*/
public void setSearchSystemEnvironment(boolean searchSystemEnvironment) {
this.searchSystemEnvironment = searchSystemEnvironment;
}
/**
* Set whether to ignore unresolvable placeholders. Default is "false":
* An exception will be thrown if a placeholder cannot be resolved.
*/
public void setIgnoreUnresolvablePlaceholders(boolean ignoreUnresolvablePlaceholders) {
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
/**
* Set a value that should be treated as null
when
* resolved as a placeholder value: e.g. "" (empty String) or "null".
*
Note that this will only apply to full property values,
* not to parts of concatenated values.
*
By default, no such null value is defined. This means that
* there is no way to express null
as a property
* value unless you explictly map a corresponding value here.
*/
public void setNullValue(String nullValue) {
this.nullValue = nullValue;
}
/**
* Only necessary to check that we're not parsing our own bean definition,
* to avoid failing on unresolvable placeholders in properties file locations.
* The latter case can happen with placeholders for system properties in
* resource locations.
* @see #setLocations
* @see org.springframework.core.io.ResourceEditor
*/
public void setBeanName(String beanName) {
this.beanName = beanName;
}
/**
* Only necessary to check that we're not parsing our own bean definition,
* to avoid failing on unresolvable placeholders in properties file locations.
* The latter case can happen with placeholders for system properties in
* resource locations.
* @see #setLocations
* @see org.springframework.core.io.ResourceEditor
*/
public void setBeanFactory(BeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (int i = 0; i < beanNames.length; i++) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(beanNames[i].equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(beanNames[i]);
try {
visitor.visitBeanDefinition(bd);
}
catch (BeanDefinitionStoreException ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), beanNames[i], ex.getMessage());
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
}
/**
* Parse the given String value recursively, to be able to resolve
* nested placeholders (when resolved property values in turn contain
* placeholders again).
* @param strVal the String value to parse
* @param props the Properties to resolve placeholders against
* @param visitedPlaceholders the placeholders that have already been visited
* during the current resolution attempt (used to detect circular references
* between placeholders). Only non-null if we're parsing a nested placeholder.
* @throws BeanDefinitionStoreException if invalid values are encountered
* @see #resolvePlaceholder(String, java.util.Properties, int)
*/
protected String parseStringValue(String strVal, Properties props, Set visitedPlaceholders)
throws BeanDefinitionStoreException {
StringBuffer buf = new StringBuffer(strVal);
int startIndex = strVal.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(buf, startIndex);
if (endIndex != -1) {
String placeholder = buf.substring(startIndex + this.placeholderPrefix.length(), endIndex);
if (!visitedPlaceholders.add(placeholder)) {
throw new BeanDefinitionStoreException(
"Circular placeholder reference '" + placeholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, props, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
String propVal = resolvePlaceholder(placeholder, props, this.systemPropertiesMode);
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, props, visitedPlaceholders);
buf.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = buf.indexOf(this.placeholderPrefix, startIndex + propVal.length());
}
else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = buf.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
}
else {
throw new BeanDefinitionStoreException("Could not resolve placeholder '" + placeholder + "'");
}
visitedPlaceholders.remove(placeholder);
}
else {
startIndex = -1;
}
}
return buf.toString();
}
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.placeholderPrefix)) {
withinNestedPlaceholder++;
index = index + this.placeholderPrefix.length();
}
else {
index++;
}
}
return -1;
}
/**
* Resolve the given placeholder using the given properties, performing
* a system properties check according to the given mode.
*
Default implementation delegates to resolvePlaceholder
* (placeholder, props)
before/after the system properties check.
*
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) {
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
/**
* Resolve the given placeholder using the given properties.
* The default implementation simply checks for a corresponding property key.
*
Subclasses can override this for customized placeholder-to-key mappings
* or custom resolution strategies, possibly just using the given properties
* as fallback.
*
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 null
if none
* @see #setSystemPropertiesMode
*/
protected String resolvePlaceholder(String placeholder, Properties props) {
return props.getProperty(placeholder);
}
/**
* 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 null
if not found
* @see #setSearchSystemEnvironment
* @see java.lang.System#getProperty(String)
* @see java.lang.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;
}
}
/**
* BeanDefinitionVisitor that resolves placeholders in String values,
* delegating to the parseStringValue
method of the
* containing class.
*/
private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final Properties props;
public PlaceholderResolvingStringValueResolver(Properties props) {
this.props = props;
}
public String resolveStringValue(String strVal) throws BeansException {
String value = parseStringValue(strVal, this.props, new HashSet());
return (value.equals(nullValue) ? null : value);
}
}
}
别人的理解:http://blog.csdn.net/turkeyzhou/article/details/2915438
在第二编对BeanFactory的分析中,我们老能看见BeanFactoyPostProcessor的身影,那么在这一节中,我们来详细的讨论一下
BeanFactoryPostProcessor的代码结构,从中学习他的优秀之处;
BeanFactoryPostProcessor能够修改bean配置文件中的bean的定义;使得我们能够进行一些额外的处理;
在spring中,我们几乎不用自己扩展这个接口,因为他内置了很多实现,但是,我们从原理上和代码上来分析其功能的实现;
一下是他的类图实现:
拿PropertyPlaceholderConfigurer举例;
PropertyPlaceholderConfigurer的功能是这样的,我们在配置文件中配置如下Bean: