Spring环境属性源配置

时间:2013-01-19 15:49:09

标签: spring

我正在使用一个名为“Config”的实用程序类的应用程序库,该实用程序类由Spring Environment对象支持,并为所有应用程序配置值提供强类型的getter。

配置的属性源可能因环境(DEV / PROD)和使用情况(独立/测试/ webapp)而异,范围可以从默认(系统和env道具)到自定义数据库和JNDI源。

我正在努力解决的问题是如何让使用此库的应用程序轻松配置Environment使用的属性源,以便可以在我们的Config类中使用这些属性并通过PropertySourcesPlaceholderConfigurer

我们仍在使用XML配置,因此理想情况下可以使用XML配置。

<bean id="propertySources" class="...">
    <property name="sources">
        <list>
            <ref local="jndiPropertySource"/>
            <ref local="databasePropertySource"/>
        </list>
    </property>
</bean>

...然后以某种方式注入到Environment的属性源集合中。

我已经读过,由于应用程序上下文生命周期的时间安排,这样的事情可能无法实现,并且可能需要使用应用程序初始化程序类来完成。

有什么想法吗?

5 个答案:

答案 0 :(得分:11)

这取决于你想要如何使用这些属性,如果要使用${propertyname}语法注入属性,那么只需让PropertySourcesPlaceHolderConfigurer工作,它可以在内部访问在环境中注册的PropertySource。 / p>

如果您计划直接使用Environment,使用说env.getProperty(),那么您是对的 - 使用PropertySourcesPlaceHolderConfigurer的属性在此处不可见。唯一的方法是使用Java代码注入它,我知道有两种方法:

一个。使用Java Config:

@Configuration
@PropertySource("classpath:/app.properties")
public class SpringConfig{

}

湾使用自定义ApplicationContextInitializer,其描述方式为here

答案 1 :(得分:9)

我提出了以下似乎有效的方法,但我对Spring很新,所以我不太确定它会在不同的使用情况下保持不变。

基本上,方法是扩展PropertySourcesPlaceholderConfigurer并添加一个setter,以允许用户轻松配置XML中的PropertySource对象列表。创建后,属性源将复制到当前Environment

这基本上允许在一个地方配置属性源,但是由placholder配置和Environment.getProperty场景使用。

扩展PropertySourcesPlaceholderConfigurer

public class ConfigSourcesConfigurer 
        extends PropertySourcesPlaceholderConfigurer
        implements EnvironmentAware, InitializingBean {

    private Environment environment;
    private List<PropertySource> sourceList;

    // Allow setting property sources as a List for easier XML configuration
    public void setPropertySources(List<PropertySource> propertySources) {

        this.sourceList = propertySources;
        MutablePropertySources sources = new MutablePropertySources();
        copyListToPropertySources(this.sourceList, sources);        
        super.setPropertySources(sources);
    }

    @Override
    public void setEnvironment(Environment environment) {
        // save off Environment for later use
        this.environment = environment;
        super.setEnvironment(environment);
    }

    @Override
    public void afterPropertiesSet() throws Exception {

        // Copy property sources to Environment
        MutablePropertySources envPropSources = ((ConfigurableEnvironment)environment).getPropertySources();
        copyListToPropertySources(this.sourceList, envPropSources);
    }

    private void copyListToPropertySources(List<PropertySource> list, MutablePropertySources sources) {

        // iterate in reverse order to insure ordering in property sources object
        for(int i = list.size() - 1; i >= 0; i--) {
            sources.addFirst(list.get(i));
        }
    }
}

显示基本配置的beans.xml文件

<beans>
    <context:annotation-config/>
    <context:component-scan base-package="com.mycompany" />

    <bean class="com.mycompany.ConfigSourcesConfigurer">
        <property name="propertySources">
            <list>
                <bean class="org.mycompany.CustomPropertySource" />
                <bean class="org.springframework.core.io.support.ResourcePropertySource">
                    <constructor-arg value="classpath:default-config.properties" />
                </bean>
            </list>
        </property>
    </bean>
    <bean class="com.mycompany.TestBean">
        <property name="stringValue" value="${placeholder}" />
    </bean>
</beans>

答案 2 :(得分:3)

以下内容适用于Spring 3.2.4。

必须静态注册

PropertySourcesPlaceholderConfigurer才能处理占位符。

自定义属性源在init方法中注册,并且由于默认属性源已经注册,因此它本身可以使用占位符进行参数化。

JavaConfig类:

@Configuration
@PropertySource("classpath:propertiesTest2.properties")
public class TestConfig {

    @Autowired
    private ConfigurableEnvironment env;

    @Value("${param:NOVALUE}")
    private String param;

    @PostConstruct
    public void init() {
        env.getPropertySources().addFirst(new CustomPropertySource(param));
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public TestBean1 testBean1() {
        return new TestBean1();
    }
 }

自定义属性来源:

    public class CustomPropertySource extends PropertySource<Object> {

    public CustomPropertySource(String param) {
        super("custom");
        System.out.println("Custom property source initialized with param " + param + ".");
    }

    @Override
    public Object getProperty(String name) {
        return "IT WORKS";
    }

}

测试bean(getValue()将输出"IT WORKS"):

public class TestBean1 {

   @Value("${value:NOVALUE}")
   private String value;

   public String getValue() {
      return value;
   }
}

答案 3 :(得分:3)

我遇到了类似的问题,在我的情况下,我在独立应用程序中使用Spring,在加载默认配置后,我可能需要应用config目录中存在的另一个属性文件(延迟加载配置)。我的解决方案受到了Spring Boot documentation的启发,但没有依赖Spring Boot。请参阅以下源代码:

@PropertySources(@PropertySource(value = "classpath:myapp-default.properties"))
public class PersistenceConfiguration {

    private final Logger log = LoggerFactory.getLogger(getClass());

    private ConfigurableEnvironment env;

    @Bean
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurerDev(ConfigurableEnvironment env) {
        return new PropertySourcesPlaceholderConfigurer();
    }


    @Autowired
    public void setConfigurableEnvironment(ConfigurableEnvironment env) {
        for(String profile: env.getActiveProfiles()) {
            final String fileName = "myapp-" + profile + ".properties";
            final Resource resource = new ClassPathResource(fileName);
            if (resource.exists()) {
                try {
                    MutablePropertySources sources = env.getPropertySources();
                    sources.addFirst(new PropertiesPropertySource(fileName,PropertiesLoaderUtils.loadProperties(resource)));
                } catch (Exception ex) {
                    log.error(ex.getMessage(), ex);
                    throw new RuntimeException(ex.getMessage(), ex);
                }
            }
        }
        this.env = env;
    }

    ...

}

答案 4 :(得分:1)

我最近遇到了如何在环境中注册自定义属性源的问题。我的具体问题是我有一个带有Spring配置的库,我希望将其导入Spring应用程序上下文,并且它需要自定义属性源。但是,我不一定能控制创建应用程序上下文的所有位置。因此,我不想使用ApplicationContextInitializer或register-before-refresh的推荐机制来注册自定义属性源。

我发现真正令人沮丧的是使用旧的PropertyPlaceholderConfigurer,很容易在Spring配置中完全子类化和自定义配置器。相比之下,为了定制属性源,我们被告知我们必须不是在Spring配置本身,而是在初始化应用程序上下文之前。

经过一些研究和反复试验,我发现 可以从Spring配置中注册自定义属性源,但是你必须要小心如何做到这一点。在任何PropertySourcesPlaceholderConfigurers在上下文中执行之前,需要注册源。您可以通过将源注册设置为具有PriorityOrdered的BeanFactoryPostProcessor以及优先于使用源的PropertySourcesPlaceholderConfigurer的订单来执行此操作。

我写了这个类,它完成了这项工作:

package example;

import java.io.IOException;
import java.util.Properties;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.PropertiesLoaderSupport;

/**
 * This is an abstract base class that can be extended by any class that wishes
 * to become a custom property source in the Spring context.
 * <p>
 * This extends from the standard Spring class PropertiesLoaderSupport, which
 * contains properties that specify property resource locations, plus methods
 * for loading properties from specified resources. These are all available to
 * be used from the Spring configuration, and by subclasses of this class.
 * <p>
 * This also implements a number of Spring flag interfaces, all of which are
 * required to maneuver instances of this class into a position where they can
 * register their property sources BEFORE PropertySourcesPlaceholderConfigurer
 * executes to substitute variables in the Spring configuration:
 * <ul>
 * <li>BeanFactoryPostProcessor - Guarantees that this bean will be instantiated
 * before other beans in the context. It also puts it in the same phase as
 * PropertySourcesPlaceholderConfigurer, which is also a BFPP. The
 * postProcessBeanFactory method is used to register the property source.</li>
 * <li>PriorityOrdered - Allows the bean priority to be specified relative to
 * PropertySourcesPlaceholderConfigurer so that this bean can be executed first.
 * </li>
 * <li>ApplicationContextAware - Provides access to the application context and
 * its environment so that the created property source can be registered.</li>
 * </ul>
 * <p>
 * The Spring configuration for subclasses should contain the following
 * properties:
 * <ul>
 * <li>propertySourceName - The name of the property source this will register.</li>
 * <li>location(s) - The location from which properties will be loaded.</li>
 * <li>addBeforeSourceName (optional) - If specified, the resulting property
 * source will be added before the given property source name, and will
 * therefore take precedence.</li>
 * <li>order (optional) - The order in which this source should be executed
 * relative to other BeanFactoryPostProcessors. This should be used in
 * conjunction with addBeforeName so that if property source factory "psfa"
 * needs to register its property source before the one from "psfb", "psfa"
 * executes AFTER "psfb".
 * </ul>
 * 
 * @author rjsmith2
 *
 */
public abstract class AbstractPropertySourceFactory extends
        PropertiesLoaderSupport implements ApplicationContextAware,
        PriorityOrdered, BeanFactoryPostProcessor {

    // Default order will be barely higher than the default for
    // PropertySourcesPlaceholderConfigurer.
    private int order = Ordered.LOWEST_PRECEDENCE - 1;

    private String propertySourceName;

    private String addBeforeSourceName;

    private ApplicationContext applicationContext;

    private MutablePropertySources getPropertySources() {
        final Environment env = applicationContext.getEnvironment();
        if (!(env instanceof ConfigurableEnvironment)) {
            throw new IllegalStateException(
                    "Cannot get environment for Spring application context");
        }
        return ((ConfigurableEnvironment) env).getPropertySources();
    }

    public int getOrder() {
        return order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public String getPropertySourceName() {
        return propertySourceName;
    }

    public void setPropertySourceName(String propertySourceName) {
        this.propertySourceName = propertySourceName;
    }

    public String getAddBeforeSourceName() {
        return addBeforeSourceName;
    }

    public void setAddBeforeSourceName(String addBeforeSourceName) {
        this.addBeforeSourceName = addBeforeSourceName;
    }

    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    /**
     * Subclasses can override this method to perform adjustments on the
     * properties after they are read.
     * <p>
     * This should be done by getting, adding, removing, and updating properties
     * as needed.
     * 
     * @param props
     *            properties to adjust
     */
    protected void convertProperties(Properties props) {
        // Override in subclass to perform conversions.
    }

    /**
     * Creates a property source from the specified locations.
     * 
     * @return PropertiesPropertySource instance containing the read properties
     * @throws IOException
     *             if properties cannot be read
     */
    protected PropertySource<?> createPropertySource() throws IOException {
        if (propertySourceName == null) {
            throw new IllegalStateException("No property source name specified");
        }

        // Load the properties file (or files) from specified locations.
        final Properties props = new Properties();
        loadProperties(props);

        // Convert properties as required.
        convertProperties(props);

        // Convert to property source.
        final PropertiesPropertySource source = new PropertiesPropertySource(
                propertySourceName, props);

        return source;
    }

    @Override
    public void postProcessBeanFactory(
            ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            // Create the property source, and get its desired position in
            // the list of sources.
            if (logger.isDebugEnabled()) {
                logger.debug("Creating property source [" + propertySourceName
                        + "]");
            }
            final PropertySource<?> source = createPropertySource();

            // Register the property source.
            final MutablePropertySources sources = getPropertySources();
            if (addBeforeSourceName != null) {
                if (sources.contains(addBeforeSourceName)) {
                    if (logger.isDebugEnabled()) {
                        logger.debug("Adding property source ["
                                + propertySourceName + "] before ["
                                + addBeforeSourceName + "]");
                    }
                    sources.addBefore(addBeforeSourceName, source);
                } else {
                    logger.warn("Property source [" + propertySourceName
                            + "] cannot be added before non-existent source ["
                            + addBeforeSourceName + "] - adding at the end");
                    sources.addLast(source);
                }
            } else {
                if (logger.isDebugEnabled()) {
                    logger.debug("Adding property source ["
                            + propertySourceName + "] at the end");
                }
                sources.addLast(source);
            }
        } catch (Exception e) {
            throw new BeanInitializationException(
                    "Failed to register property source", e);
        }
    }

}

值得注意的是,此属性源工厂类的默认顺序优先于PropertySourcesPlaceholderConfigurer的默认顺序。

此外,属性源的注册发生在postProcessBeanFactory中,这意味着它将以相对于PropertySourcesPlaceholderConfigurer的正确顺序执行。我发现InitializingBean和afterPropertiesSet不遵守order参数的困难方式,我放弃了这种方法是错误和多余的。

最后,因为这是一个BeanFactoryPostProcessor,所以尝试连接依赖关系的方式是一个坏主意。因此,该类直接通过应用程序上下文访问环境,它使用ApplicationContextAware获取。

在我的情况下,我需要属性源来解密密码属性,我使用以下子类实现:

package example;

import java.util.Properties;

/**
 * This is a property source factory that creates a property source that can
 * process properties for substituting into a Spring configuration.
 * <p>
 * The only thing that distinguishes this from a normal Spring property source
 * is that it decrypts encrypted passwords.
 * 
 * @author rjsmith2
 *
 */
public class PasswordPropertySourceFactory extends
        AbstractPropertySourceFactory {

    private static final PasswordHelper passwordHelper = new PasswordHelper();

    private String[] passwordProperties;

    public String[] getPasswordProperties() {
        return passwordProperties;
    }

    public void setPasswordProperties(String[] passwordProperties) {
        this.passwordProperties = passwordProperties;
    }

    public void setPasswordProperty(String passwordProperty) {
        this.passwordProperties = new String[] { passwordProperty };
    }

    @Override
    protected void convertProperties(Properties props) {
        // Adjust password fields by decrypting them.
        if (passwordProperties != null) {
            for (String propName : passwordProperties) {
                final String propValue = props.getProperty(propName);
                if (propValue != null) {
                    final String plaintext = passwordHelper
                            .decryptString(propValue);
                    props.setProperty(propName, plaintext);
                }
            }
        }
    }

}

最后,我在Spring配置中指定了属性源工厂:

<!-- Enable property resolution via PropertySourcesPlaceholderConfigurer. 
    The order has to be larger than the ones used by custom property sources 
    so that those property sources are registered before any placeholders
    are substituted. -->
<context:property-placeholder order="1000" ignore-unresolvable="true" />

<!-- Register a custom property source that reads DB properties, and
     decrypts the database password. -->
<bean class="example.PasswordPropertySourceFactory">
    <property name="propertySourceName" value="DBPropertySource" />
    <property name="location" value="classpath:db.properties" />
    <property name="passwordProperty" value="db.password" />
    <property name="ignoreResourceNotFound" value="true" />

    <!-- Order must be lower than on property-placeholder element. -->
    <property name="order" value="100" />
</bean>

老实说,使用PropertySourcesPlaceholderConfigurer和AbstractPropertySourceFactory中的顺序默认值,甚至可能不需要在Spring配置中指定顺序。

尽管如此,这仍然有效,并且它需要对应用程序上下文初始化进行任何调整。