Очень полезную штучку написал в ходе текущего проекта. Позволяет видеть свойства из файла или системные как бины соответствующих типов (поддерживается конверсия для String, Integer и Boolean). Это принципиально отличается от org.springframework.beans.factory.config.PropertyPlaceholderConfigurerа, который просто заполняет бины нужными полями.
Еще я добавил возможность замены placeholder в самих значениях свойств, например
myProperty=path to java home is {java.home}
будет заменен на соответствующее значение.
Вот код сего незамысловатого сооружения:
package com.anydoby.spring;
import java.io.IOException;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.StringUtils;
/**
* PropertyPlaceholderConfigurer for reusable properties. Normally, you cannot
* retrieve the properties from the PropertyPlaceholderConfigurer but this
* version has a getProperties() method to do just that.
*/
public class ReusablePropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer implements
ApplicationContextAware {
private Properties properties;
private final static Pattern HOLDER_PATTERN = Pattern.compile("[$][{]([^}]*)[}]");
private static final Pattern INTEGER_PATTERN = Pattern.compile("\\d{1," + Integer.toString(Integer.MAX_VALUE).length()
+ "}");
private Properties commandLineArgs = new Properties();
private BeanDefinitionRegistry registry;
/**
* @return the <code>commandLineArgs</code> property
*/
public final Properties getCommandLineArgs() {
return this.commandLineArgs;
}
/**
* @param commandLineArgs
* the commandLineArgs to set the property to
*/
public final void setCommandLineArgs(final Properties commandLineArgs) {
this.commandLineArgs = commandLineArgs;
}
/**
* Delegates the loading to the standard {@link PropertyPlaceholderConfigurer}
* but also saves the properties so we can reuse them. The saved properties are
* processed to resolve references to other properties.
*
* @param properties
* to load
*/
@Override
public void loadProperties(final Properties properties) throws IOException {
if (this.commandLineArgs != null) {
properties.putAll(this.commandLineArgs);
}
this.properties = new Properties();
super.loadProperties(properties);
for (final Entry<Object, Object> entry : properties.entrySet()) {
if (!this.properties.containsKey(entry.getKey())) {
this.properties.put(entry.getKey(), entry.getValue());
}
}
for (final Entry<Object, Object> entry : this.properties.entrySet()) {
final String newValue = resolveValueProperties(this.properties, (String) entry.getValue());
this.properties.put(entry.getKey(), newValue);
}
if (this.registry != null) {
for (final Entry<Object, Object> entry : this.properties.entrySet()) {
final Object key = entry.getKey();
final String beanName = key.toString();
final Object value = entry.getValue();
final String stringValue = value.toString();
final BeanDefinitionBuilder beanDefinitionBuilder;
if (isBoolean(stringValue)) {
beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Boolean.class);
} else if (isInteger(stringValue)) {
beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Integer.class);
} else {
beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(String.class);
}
beanDefinitionBuilder.addConstructorArgValue(stringValue);
beanDefinitionBuilder.setScope(BeanDefinition.SCOPE_SINGLETON);
final AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
final BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, beanName);
this.logger.debug("Registering " + beanName + " with value " + stringValue + " as "
+ beanDefinition.getBeanClassName());
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
}
}
private boolean isInteger(final String stringValue) {
return INTEGER_PATTERN.matcher(stringValue).matches();
}
private boolean isBoolean(final String stringValue) {
return Boolean.TRUE.toString().equals(stringValue) || Boolean.FALSE.toString().equals(stringValue);
}
/**
* Returns all properties that have been loaded.
*
* @return loaded properties
*/
public Properties getProperties() {
return this.properties;
}
/**
* Resolve the references to properties in the specified value.
* <p/>
* Example: value = "${foo} ${bar}" and properties containing a value "Hello"
* for key "foo" and containing a value "world" for key "bar" will result in
* "Hello world".
*
* @param properties
* the properties to look up values.
* @param value
* the value to resolve. Possibly containing references.
* @return the resolved value.
*/
public static String resolveValueProperties(final Properties properties, final String value) {
final Matcher matcher = HOLDER_PATTERN.matcher(value);
final StringBuffer sb = new StringBuffer();
while (matcher.find()) {
final String holder = matcher.group(0);
final String holderName = matcher.group(1);
final String replacement;
if (!StringUtils.hasText(holderName)) {
final String property = properties.getProperty(holderName);
if (property != null) {
replacement = property;
} else if (System.getProperty(holderName) != null) {
replacement = System.getProperty(holderName);
} else {
replacement = holder;
}
} else {
replacement = holder;
}
matcher.appendReplacement(sb, Matcher.quoteReplacement(replacement));
}
matcher.appendTail(sb);
return sb.toString();
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
findRegistry(applicationContext);
}
private void findRegistry(final ApplicationContext applicationContext) {
if (applicationContext == null) {
this.logger.warn("Unable to find an application context which provides BeanDefinitionRegistry functionality. "
+ "Properties will not be exposed as beans.");
return;
}
final AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
if (beanFactory instanceof BeanDefinitionRegistry) {
this.registry = (BeanDefinitionRegistry) beanFactory;
} else {
findRegistry(applicationContext.getParent());
}
}
}
А вот тут можно посмотреть, как это применят на практике. Смотрим юнит тест com.anydoby.spring.TestOptionalProperty.
