Spring 环境属性源配置
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14416005/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring Environment Property Source Configuration
提问by WayneC
I'm working on an application library with a utility class called "Config" which is backed by the Spring Environmentobject and provides strongly typed getters for all the applications configuration values.
我正在使用一个名为“ Config”的实用程序类开发一个应用程序库,该类由 SpringEnvironment对象支持,并为所有应用程序配置值提供强类型的 getter。
The property sources for the configuration can vary depending on environment (DEV/PROD) and usage (standalone/test/webapp), and can range from the default ones (system & env props) to custom database and JNDI sources.
配置的属性源可能因环境(DEV/PROD)和使用(独立/测试/webapp)而异,范围可以从默认的(系统和环境道具)到自定义数据库和 JNDI 源。
What I'm struggling with is how to let the apps consuming this library easily configure the property source(s) used by Environment, such that the properties are available for use in our Configclass and via the PropertySourcesPlaceholderConfigurer.
我正在努力解决的是如何让使用此库的应用程序轻松配置 使用的属性源Environment,以便这些属性可用于我们的Config类并通过PropertySourcesPlaceholderConfigurer.
We're still using XML configuration, so ideally this could be configured in XML something like.
我们仍在使用 XML 配置,因此理想情况下可以在 XML 中配置类似的内容。
<bean id="propertySources" class="...">
<property name="sources">
<list>
<ref local="jndiPropertySource"/>
<ref local="databasePropertySource"/>
</list>
</property>
</bean>
...and then injected somehow into the Environment's property sources collection.
...然后以某种方式注入到 Environment 的属性源集合中。
I've read that something like this may not be possible due to the timing of the app context lifecycle, and that this may need to be done using an application initializer class.
我已经读过,由于应用程序上下文生命周期的时间安排,这样的事情可能是不可能的,并且这可能需要使用应用程序初始值设定项类来完成。
Any ideas?
有任何想法吗?
采纳答案by WayneC
I came up with the following which seems to work, but I'm fairly new to Spring, so I'm not so sure how it will hold up under different use cases.
我想出了以下似乎有效的方法,但我对 Spring 还很陌生,所以我不太确定它在不同的用例下会如何。
Basically, the approach is to extend PropertySourcesPlaceholderConfigurerand add a setter to allow the user to easily configure a List of PropertySourceobjects in XML. After creation, the property sources are copied to the current Environment.
基本上,该方法是扩展PropertySourcesPlaceholderConfigurer和添加一个 setter 以允许用户轻松配置PropertySourceXML 中的对象列表。创建后,属性源将复制到当前Environment.
This basically allows the property sources to be configured in one place, but used by both placholder configuration and Environment.getProperty scenarios.
这基本上允许在一个地方配置属性源,但由占位符配置和 Environment.getProperty 场景使用。
Extended PropertySourcesPlaceholderConfigurer
扩展 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 file showing basic configuration
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>
回答by Biju Kunjummen
It depends on how you want to use the properties, if it is to inject the properties using ${propertyname}syntax, then yes just having PropertySourcesPlaceHolderConfigurer will work, which internally has access to the PropertySources registered in the environment.
这取决于您想如何使用属性,如果要使用${propertyname}语法注入属性,那么是的,只需使用 PropertySourcesPlaceHolderConfigurer 即可,它可以在内部访问环境中注册的 PropertySources。
If you plan to use Environment directly, using say env.getProperty(), then you are right - the properties using PropertySourcesPlaceHolderConfigurer are not visible here. The only way then is to inject it using Java code, there are two ways that I know of:
如果您打算直接使用 Environment ,使用 say env.getProperty(),那么您是对的 - 使用 PropertySourcesPlaceHolderConfigurer 的属性在此处不可见。唯一的方法是使用 Java 代码注入它,我知道有两种方法:
a. Using Java Config:
一种。使用 Java 配置:
@Configuration
@PropertySource("classpath:/app.properties")
public class SpringConfig{
}
b. Using a custom ApplicationContextInitializer, the way it is described here
湾 使用自定义ApplicationContextInitializer,这里描述的方式
回答by Wellington Souza
I had a similar problem, in my case I'm using Springin a standalone application, after load the default configurations I may need apply another properties file (lazy load configs) present in a config directory. My solution was inspired this Spring Bootdocumentation, but with no dependency of Spring Boot. See below the source code:
我有一个类似的问题,在我的情况下,我Spring在独立应用程序中使用,在加载默认配置后,我可能需要应用配置目录中存在的另一个属性文件(延迟加载配置)。我的解决方案受到了这个Spring Boot文档的启发,但不依赖于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;
}
...
}
回答by Agustí Sánchez
The following worked for me with Spring 3.2.4 .
以下内容适用于 Spring 3.2.4 。
PropertySourcesPlaceholderConfigurermust be registered statically in order to process the placeholders.
PropertySourcesPlaceholderConfigurer必须静态注册才能处理占位符。
The custom property source is registered in the initmethod and as the default property sources are already registered, it can itself be parameterized using placeholders.
自定义属性源在init方法中注册,并且由于默认属性源已经注册,因此可以使用占位符对其本身进行参数化。
JavaConfig class:
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();
}
}
Custom property source:
自定义属性源:
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";
}
}
Test bean (getValue()will output "IT WORKS"):
测试 bean(getValue()将输出"IT WORKS"):
public class TestBean1 {
@Value("${value:NOVALUE}")
private String value;
public String getValue() {
return value;
}
}
回答by Richard J. Smith
I recently ran into the issue of how to register custom property sources in the environment. My specific problem is that I have a library with a Spring configuration that I want to be imported into the Spring application context, and it requires custom property sources. However, I don't necessarily have control over all of the places where the application context is created. Because of this, I do not want to use the recommended mechanisms of ApplicationContextInitializer or register-before-refresh in order to register the custom property sources.
我最近遇到了如何在环境中注册自定义属性源的问题。我的具体问题是我有一个带有 Spring 配置的库,我想将其导入 Spring 应用程序上下文,并且它需要自定义属性源。但是,我不一定能够控制创建应用程序上下文的所有位置。因此,我不想使用推荐的 ApplicationContextInitializer 或 register-before-refresh 机制来注册自定义属性源。
What I found really frustrating is that using the old PropertyPlaceholderConfigurer, it was easy to subclass and customize the configurers completely within the Spring configuration. In contrast, to customize property sources, we are told that we have to do it not in the Spring configuration itself, but before the application context is initialized.
我发现真正令人沮丧的是,使用旧的 PropertyPlaceholderConfigurer,很容易在 Spring 配置中完全子类化和自定义配置器。相反,要自定义属性源,我们被告知不能在 Spring 配置本身中执行此操作,而是在初始化应用程序上下文之前执行此操作。
After some research and trial and error, I discovered that it ispossible to register custom property sources from inside of the Spring configuration, but you have to be careful how you do it. The sources need to be registered before any PropertySourcesPlaceholderConfigurers execute in the context. You can do this by making the source registration a BeanFactoryPostProcessor with PriorityOrdered and an order that is higher precedence than the PropertySourcesPlaceholderConfigurer that uses the sources.
一些研究和反复试验后,我发现它是可以从Spring配置的内部寄存器自定义属性的来源,但你有你如何做到这一点要小心。在上下文中执行任何 PropertySourcesPlaceholderConfigurers 之前,需要注册源。您可以通过使用 PriorityOrdered 和比使用源的 PropertySourcesPlaceholderConfigurer 更高优先级的顺序使源注册成为 BeanFactoryPostProcessor 来实现此目的。
I wrote this class, which does the job:
我写了这个类,它完成了这项工作:
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);
}
}
}
Of note here is that the default order of this property source factory class is higher precedence than the default order of PropertySourcesPlaceholderConfigurer.
这里需要注意的是,这个属性源工厂类的默认顺序比 PropertySourcesPlaceholderConfigurer 的默认顺序更高。
Also, the registration of the property source happens in postProcessBeanFactory, which means that it will execute in the correct order relative to the PropertySourcesPlaceholderConfigurer. I discovered the hard way that InitializingBean and afterPropertiesSet do not respect the order parameter, and I gave up on that approach as being wrong and redundant.
此外,属性源的注册发生在 postProcessBeanFactory 中,这意味着它将以相对于 PropertySourcesPlaceholderConfigurer 的正确顺序执行。我发现 InitializingBean 和 afterPropertiesSet 不尊重 order 参数的困难方式,我放弃了这种方法,因为它是错误和多余的。
Finally, because this is a BeanFactoryPostProcessor, it is a bad idea to try to wire much in the way of dependencies. Therefore, the class accesses the environment directly through the application context, which it obtains using ApplicationContextAware.
最后,因为这是一个 BeanFactoryPostProcessor,所以尝试以依赖的方式连接很多东西是一个坏主意。因此,该类直接通过应用程序上下文访问环境,它使用 ApplicationContextAware 获取。
In my case, I needed the property source to decrypt password properties, which I implemented using the following subclass:
在我的情况下,我需要属性源来解密密码属性,我使用以下子类实现:
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);
}
}
}
}
}
Finally, I specifed the property source factory in my Spring configuration:
最后,我在 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>
To be honest, with the defaults for order in PropertySourcesPlaceholderConfigurer and AbstractPropertySourceFactory, it is probably not even necessary to specify order in the Spring configuration.
老实说,使用 PropertySourcesPlaceholderConfigurer 和 AbstractPropertySourceFactory 中的默认顺序,甚至可能不需要在 Spring 配置中指定顺序。
Nonetheless, this works, and it does notrequire any fiddling with the application context initialization.
尽管如此,这个作品,它并不需要与应用程序上下文初始化任何摆弄。

