Русский English Тэги View Sergey Zolotaryov's profile on LinkedIn Вход
Переиспользуемые свойства в Spring
Постоянная ссылка 27-07-2010 anydoby java spring

Очень полезную штучку написал в ходе текущего проекта. Позволяет видеть свойства из файла или системные как бины соответствующих типов (поддерживается конверсия для 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.

Добавить комментарий