Spring Environment Source Configuration

I am working on an application library with a utility class called " Config ", which is supported by the Spring Environment object and provides strongly typed getters for all application configuration values.

Sources of resources for configuration can vary depending on the environment (DEV / PROD) and usage (stand-alone / test / webapp) and can vary from standard (system and env details) to custom databases and JNDI sources.

What I'm struggling with is letting the applications using this library easily configure the property sources (s) used by the Environment so that the properties are available for use in our Config class and through PropertySourcesPlaceholderConfigurer .

We still use the XML configuration, so ideally this can be configured in XML form.

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

... and then somehow introduced into the collection of sources of environmental properties.

I read that something like this may not be possible due to the application context life cycle time, and that this may need to be done using the application initializer class.

Any ideas?

+7
source share
5 answers

I came up with the following, which seems to work, but I'm pretty new to Spring, so I'm not sure how it will be supported in different use cases.

Basically, the approach is to extend the PropertySourcesPlaceholderConfigurer and add an installer that allows the user to easily customize List objects from PropertySource to XML. Once created, property sources are copied to the current Environment .

This basically allows you to configure resource sources in one place, but it is used with both placholder configuration and Environment.getProperty scripts.

Extended 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 with basic configuration

 <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> 
+7
source

It depends on how you want to use the properties, if you need to enter properties using the syntax ${propertyname} , then yes, PropertySourcesPlaceHolderConfigurer will work, which internally has access to resources registered in the environment.

If you plan to use the environment directly using say env.getProperty() , then you are right - properties using PropertySourcesPlaceHolderConfigurer are not visible here. The only way is to enter it using Java code, there are two ways that I know:

but. Using Java Config:

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

b. Using a custom ApplicationContextInitializer as described here

+11
source

The following worked for me with Spring 3.2.4.

PropertySourcesPlaceholderConfigurer must be registered statically to handle placeholders.

The source of the custom property is registered in the init method, and since property sources are already registered by default, it can itself be parameterized using placeholders.

JavaConfig Class:

 @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 Property:

  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"; } } 

The bean test ( getValue() will output "IT WORKS" ):

 public class TestBean1 { @Value("${value:NOVALUE}") private String value; public String getValue() { return value; } } 
+2
source

I had a similar problem, in my case I use Spring in a standalone application, after loading the default configurations, I may need to apply another properties file (lazy load configs) present in the configuration directory. My solution was inspired by this Spring Boot documentation , but without dependency on Spring Boot . See below source code:

 @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; } ... } 
+1
source

I recently ran into the problem of registering custom property sources in the environment. My problem is that I have a Spring configuration library that I want to import into the Spring application context, and this requires its own property sources. However, I do not necessarily control all places where the application context is created. Because of this, I do not want to use the recommended ApplicationContextInitializer or register-before-refresh mechanisms to register custom property sources.

What I really upset was that using the old PropertyPlaceholderConfigurer, it was easy to subclass and configure the configurators completely in the Spring configuration. In contrast, to configure property sources, we are told that we should not do this in the Spring configuration itself, but before initializing the application context.

After some research and testing and errors, I found that you can register custom property sources from within the Spring configuration, but you have to be careful how you do it. Sources must be registered before any PropertySourcesPlaceholderConfigurers object executes in context. You can do this by doing the initial registration of a BeanFactoryPostProcessor with PriorityOrdered and an order that has a higher priority than PropertySourcesPlaceholderConfigurer, which uses sources.

I wrote this class that 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); } } } 

It should be noted that the default order of this factory property class takes precedence over the default order for PropertySourcesPlaceholderConfigurer by default.

In addition, the registration of the property source occurs in postProcessBeanFactory, which means that it will be executed in the correct order relative to PropertySourcesPlaceholderConfigurer. I found the hard way that InitializingBean and afterPropertiesSet do not take into account the order parameter, and I abandoned this approach as erroneous and redundant.

Finally, since this is a BeanFactoryPostProcessor, it is a bad idea to try to link a lot depending on dependencies. Therefore, the class accesses the environment directly through the application context, which it receives using ApplicationContextAware.

In my case, I needed a property source to decrypt the 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 pointed out the source of the factory properties in my Spring configuration:

 <!-- 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 default settings in PropertySourcesPlaceholderConfigurer and AbstractPropertySourceFactory, you might not even need to specify the order in the Spring configuration.

However, this works, and it does not require any attempt to initialize the application context.

0
source

All Articles