List distributed properties in Spring Boot - spring-boot

Code sample below is from a Spring Boot application that obtains property definitions from the local application.properties file. Additional or overriding property definitions are obtained from a remote Consul server. The goal is to have the application enumerate all property definitions regardless of the source. Example setup:
The local application.properties file contains this property definition
spring.cloud.stream.bindings.breakoutOutputChannel.destination=breakout_topic_local
The overriding definition for the same property in the Consul server is:
spring.cloud.stream.bindings.breakoutOutputChannel.destination=breakout_topic_production
In the same code I am using two mechanisms to access the same property value. First is use of the #Value annotation, the second is querying properties obtained from the environment. The means of obtaining the applications enviroment properties is also show below.
When using the #Value annotation the proper override value is obtained. But when accessing the property definition directly only the value from the local file is available. Example show below.
Is it possible to obtain the same Map of properties that the #Value annotation uses so that its contents can be enumerated?
Sample code:
#Value("${spring.cloud.stream.bindings.breakoutOutputChannel.destination}")
String breakout;
#EventListener
public void onApplicationEvent(ApplicationReadyEvent event) {
Properties props =applicationProperties(event.getApplicationContext().getEnvironment());
log.info("Property spring.cloud.stream.bindings.breakoutOutputChannel.destination");
log.info( " value from bound var = " +breakout);
log.info( " value from properties = " +props.getProperty("spring.cloud.stream.bindings.breakoutOutputChannel.destination"));
}
public Properties applicationProperties(Environment env) {
final Properties properties = new Properties();
for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof PropertiesPropertySource) {
log.info("Adding all properties contained in " + propertySource.getName());
properties.putAll(((MapPropertySource) propertySource).getSource());
}
if (propertySource instanceof CompositePropertySource){
properties.putAll(getPropertiesInCompositePropertySource((CompositePropertySource) propertySource));
}
}
return properties;
}
private Properties getPropertiesInCompositePropertySource(CompositePropertySource compositePropertySource){
final Properties properties = new Properties();
compositePropertySource.getPropertySources().forEach(propertySource -> {
if (propertySource instanceof MapPropertySource) {
log.info("Adding all properties contained in " + propertySource.getName());
properties.putAll(((MapPropertySource) propertySource).getSource());
}
if (propertySource instanceof CompositePropertySource)
properties.putAll(getPropertiesInCompositePropertySource((CompositePropertySource) propertySource));
});
return properties;
}
Sample output
Adding all properties contained in applicationConfig: [file:./application.properties]
Adding all properties contained in applicationConfig: [classpath:/application.properties]
Property spring.cloud.stream.bindings.breakoutOutputChannel.destination
value from bound var = breakout_topic_production
value from properties = breakout_topic_local

While the generated properties object, created in the above example, source is the application Environment, the values are indeed only those that are included in the local property definition files.
However if the property key is used to get the value directly from the Environment, the the value returned will contain any override distributed to the application from Consul. From the example the return value from
event.getApplicationContext().getEnvironment().getProperty("spring.cloud.stream.bindings.breakoutOutputChannel.destination"));
will return the correct property value.

Related

Custom property loader with Spring Cloud Config

I'm using Spring Cloud Config in my spring-boot application and I need to write some custom code to handle properties to be read from my corporate password vault when property is flagged as such. I know spring cloud supports Hashicorp Vault, but that's not the one in case.
I don't want to hard-code specific properties to be retrieved from a different source, for example, I would have a properties file for application app1 with profile dev with values:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=dbuser
spring.datasource.password=dbpass
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
but for some other profiles such as prod, I would have:
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=prod-user
spring.datasource.password=[[vault]]
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
So I need the custom property vault to intercept the property loaded whenever it finds a returned value equals to [[vault]] (or some other type of flag), and query from the corporate vault instead. In this case, my custom property loader would find the value of property spring.datasource.password from the corporate password vault. All other properties would still be returned as-is from values loaded by standard spring cloud config client.
I would like to do that using annotated code only, no XML configuration.
You can implement your own PropertySourceLocator and add entry to
spring.factories in directory META-INF.
#spring.factories
org.springframework.cloud.bootstrap.BootstrapConfiguration=/
foo.bar.MyPropertySourceLocator
Then you can you can refer to keys in your corporate password vault like a normal properties in spring.
spring.datasource.url=jdbc:mysql://localhost/test
spring.datasource.username=prod-user
spring.datasource.password=${lodaded.password.from.corporate.vault}
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
Implementation by HasiCorp: VaultPropertySourceLocatorSupport
While trying to solve the identical problem, I believe that I have come to work-around that may be acceptable.
Here is my solution below.
public class JBossVaultEnvironmentPostProcessor implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
MutablePropertySources propertySources = environment.getPropertySources();
Map<String, String> sensitiveProperties = propertySources.stream()
.filter(propertySource -> propertySource instanceof EnumerablePropertySource)
.map(propertySource -> (EnumerablePropertySource<?>) propertySource)
.map(propertySource -> {
Map<String, String> vaultProperties = new HashMap<>();
String[] propertyNames = propertySource.getPropertyNames();
for (String propertyName : propertyNames) {
String propertyValue = propertySource.getProperty(propertyName).toString();
if (propertyValue.startsWith("VAULT::")) {
vaultProperties.put(propertyName, propertyValue);
}
}
return vaultProperties;
})
.reduce(new HashMap<>(), (m1, m2) -> {
m1.putAll(m2);
return m1;
});
Map<String, Object> vaultProperties = new HashMap<>();
sensitiveProperties.keySet().stream()
.forEach(key -> {
vaultProperties.put(key, VaultReader.readAttributeValue(sensitiveProperties.get(key)));
});
propertySources.addFirst(new MapPropertySource("vaultProperties", vaultProperties));
}

How to read all properties values from the source properties file in Spring-cloud-config client

i have this spring-cloud-config client class and i can access the individual properties using the #Value annotation just fine. However, i am interested to know how to read ALL the properties values from a properties file without binding each of the property's key to a #Value annotation. Basically the idea is that i would like to read all the properties value from the properties file without even knowing anything about the properties defined in the file. Any idea how i can do that?
Client Class
#EnableAutoConfiguration
#ComponentScan
#RestController
#RefreshScope
public class ConfigDemoClientApplication
{
#Value("${special}")
String special;
#RequestMapping("/restaurant")
public String hello()
{
return "Hello " + special;
}
public static void main(String[] args) {
SpringApplication.run(ConfigDemoClientApplication.class, args);
}
}
Sample Properties file
special: bargain!
amount: 200
city: New York
In this example, i would like to read all the 3 properties without defining a #Value annotation for each of them in my class. Is that possible?
Thanks for your help.
I just solved you problem creating this applicationProps bean, that is a java.util.Properties object containing all the properties of the application.
The only think needed is an autowired Environment object.
Here's the code:
#Autowired
Environment env;
//Load all the properties of the server and put them into a java Properties obj
#Bean(name = "applicationProps")
public Properties applicationProperties() {
final Properties properties = new Properties();
for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof PropertiesPropertySource) {
log.info("Adding all properties contained in " + propertySource.getName());
properties.putAll(((MapPropertySource) propertySource).getSource());
}
if (propertySource instanceof CompositePropertySource){
properties.putAll(getPropertiesInCompositePropertySource((CompositePropertySource) propertySource));
}
}
return properties;
}
private Properties getPropertiesInCompositePropertySource(CompositePropertySource compositePropertySource){
final Properties properties = new Properties();
compositePropertySource.getPropertySources().forEach(propertySource -> {
if (propertySource instanceof MapPropertySource) {
log.info("Adding all properties contained in " + propertySource.getName());
properties.putAll(((MapPropertySource) propertySource).getSource());
}
if (propertySource instanceof CompositePropertySource)
properties.putAll(getPropertiesInCompositePropertySource((CompositePropertySource) propertySource));
});
return properties;
}
#Autowired
#Qualifier("applicationProps")
Properties applicationProperties;
The recursive step in getPropertiesInCompositePropertySource method is needed because the properties fetched from the config server are recursively nested in a CompositePropertySource
Hope it helps
Greetings
Try this : Its all Spring, you can maybe use it with a PostConstruct Method
Map<String,String> someMap = new HashMap<>();
Resource resource = new ClassPathResource("some.properties");
Properties props = PropertiesLoaderUtils.loadProperties(resource);
for(Object key : props.keySet()) {
someMap.put(key.toString(),props.getProperty(key.toString()));
}

Dump Spring boot Configuration

Our Ops guys want the Spring boot configuration (i.e. all properties) to be dumped to the log file when the app starts. I assume this can be done by injecting the properties with annotation #ConfigurationProperties and printing them.
The questions is whether there is a better or built-in mechanism to achieve this.
Given there does not seem to be a built in solution besides, I was try to cook my own. Here is what I came up with:
#Component
public class ConfigurationDumper {
#Autowired
public void init(Environment env){
log.info("{}",env);
}
}
The challenge with this is that it does not print variables that are in my application.yml. Instead, here is what I get:
StandardServletEnvironment
{
activeProfiles=[],
defaultProfiles=[default],
propertySources=[
servletConfigInitParams,
servletContextInitParams,
systemProperties,
systemEnvironment,
random,
applicationConfig: [classpath: /application.yml]
]
}
How can I fix this so as to have all properties loaded and printed?
If you use actuator , env endpoint will give you all the configuration properties set in ConfigurableEnvironment and configprops will give you the list of #ConfigurationProperties, but not in the log.
Take a look at the source code for this env endpoint, may be it will give you an idea of how you could get all the properties you are interested in.
There is no built-in mechanism and it really depends what you mean by "all properties". Do you want only the actual keys that you wrote or you want all properties (including defaults).
For the former, you could easily listen for ApplicationEnvironmentPreparedEvent and log the property sources you're interested in. For the latter, /configprops is indeed a much better/complete output.
This logs only the properties configured *.properties file.
/**
* maps given property names to its origin
* #return a map where key is property name and value the origin
*/
public Map<String, String> fromWhere() {
final Map<String, String> mapToLog = new HashMap<>();
final MutablePropertySources propertySources = env.getPropertySources();
final Iterator<?> it = propertySources.iterator();
while (it.hasNext()) {
final Object object = it.next();
if (object instanceof MapPropertySource) {
MapPropertySource propertySource = (MapPropertySource) object;
String propertySourceName = propertySource.getName();
if (propertySourceName.contains("properties")) {
Map<String, Object> sourceMap = propertySource.getSource();
for (String key : sourceMap.keySet()) {
final String envValue = env.getProperty(key);
String env2Val = System.getProperty(key);
String source = propertySource.getName().contains("file:") ? "FILE" : "JAR";
if (envValue.equals(env2Val)) {
source = "ENV";
}
mapToLog.putIfAbsent(key, source);
}
}
}
}
return mapToLog;
}
my example output which depicts the property name, value and from where it comes. My property values are describing from where they come.:
myprop: fooFromJar from JAR
aPropFromFile: fromExternalConfFile from FILE
mypropEnv: here from vm arg from ENV
ENV means that I have given it by -D to JVM.
JAR means it is from application.properties inside JAR
FILE means it is from application.properties outside JAR

Access all Environment properties as a Map or Properties object

I am using annotations to configure my spring environment like this:
#Configuration
...
#PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer
{
#Autowired
Environment env;
}
This leads to my properties from default.properties being part of the Environment. I want to use the #PropertySource mechanism here, because it already provides the possibility to overload properties through several fallback layers and different dynamic locations, based on the environment settings (e.g. config_dir location). I just stripped the fallback to make the example easier.
However, my problem now is that I want to configure for example my datasource properties in default.properties. You can pass the settings to the datasource without knowing in detail what settings the datasource expects using
Properties p = ...
datasource.setProperties(p);
However, the problem is, the Environment object is neither a Properties object nor a Map nor anything comparable. From my point of view it is simply not possible to access all values of the environment, because there is no keySet or iterator method or anything comparable.
Properties p <=== Environment env?
Am I missing something? Is it possible to access all entries of the Environment object somehow? If yes, I could map the entries to a Map or Properties object, I could even filter or map them by prefix - create subsets as a standard java Map ... This is what I would like to do. Any suggestions?
You need something like this, maybe it can be improved. This is a first attempt:
...
import org.springframework.core.env.PropertySource;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.core.env.Environment;
import org.springframework.core.env.MapPropertySource;
...
#Configuration
...
#org.springframework.context.annotation.PropertySource("classpath:/config/default.properties")
...
public class GeneralApplicationConfiguration implements WebApplicationInitializer
{
#Autowired
Environment env;
public void someMethod() {
...
Map<String, Object> map = new HashMap();
for(Iterator it = ((AbstractEnvironment) env).getPropertySources().iterator(); it.hasNext(); ) {
PropertySource propertySource = (PropertySource) it.next();
if (propertySource instanceof MapPropertySource) {
map.putAll(((MapPropertySource) propertySource).getSource());
}
}
...
}
...
Basically, everything from the Environment that's a MapPropertySource (and there are quite a lot of implementations) can be accessed as a Map of properties.
This is an old question, but the accepted answer has a serious flaw. If the Spring Environment object contains any overriding values (as described in Externalized Configuration), there is no guarantee that the map of property values it produces will match those returned from the Environment object. I found that simply iterating through the PropertySources of the Environment did not, in fact, give any overriding values. Instead it produced the original value, the one that should have been overridden.
Here is a better solution. This uses the EnumerablePropertySources of the Environment to iterate through the known property names, but then reads the actual value out of the real Spring environment. This guarantees that the value is the one actually resolved by Spring, including any overriding values.
Properties props = new Properties();
MutablePropertySources propSrcs = ((AbstractEnvironment) springEnv).getPropertySources();
StreamSupport.stream(propSrcs.spliterator(), false)
.filter(ps -> ps instanceof EnumerablePropertySource)
.map(ps -> ((EnumerablePropertySource) ps).getPropertyNames())
.flatMap(Arrays::<String>stream)
.forEach(propName -> props.setProperty(propName, springEnv.getProperty(propName)));
I had the requirement to retrieve all properties whose key starts with a distinct prefix (e.g. all properties starting with "log4j.appender.") and wrote following Code (using streams and lamdas of Java 8).
public static Map<String,Object> getPropertiesStartingWith( ConfigurableEnvironment aEnv,
String aKeyPrefix )
{
Map<String,Object> result = new HashMap<>();
Map<String,Object> map = getAllProperties( aEnv );
for (Entry<String, Object> entry : map.entrySet())
{
String key = entry.getKey();
if ( key.startsWith( aKeyPrefix ) )
{
result.put( key, entry.getValue() );
}
}
return result;
}
public static Map<String,Object> getAllProperties( ConfigurableEnvironment aEnv )
{
Map<String,Object> result = new HashMap<>();
aEnv.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) );
return result;
}
public static Map<String,Object> getAllProperties( PropertySource<?> aPropSource )
{
Map<String,Object> result = new HashMap<>();
if ( aPropSource instanceof CompositePropertySource)
{
CompositePropertySource cps = (CompositePropertySource) aPropSource;
cps.getPropertySources().forEach( ps -> addAll( result, getAllProperties( ps ) ) );
return result;
}
if ( aPropSource instanceof EnumerablePropertySource<?> )
{
EnumerablePropertySource<?> ps = (EnumerablePropertySource<?>) aPropSource;
Arrays.asList( ps.getPropertyNames() ).forEach( key -> result.put( key, ps.getProperty( key ) ) );
return result;
}
// note: Most descendants of PropertySource are EnumerablePropertySource. There are some
// few others like JndiPropertySource or StubPropertySource
myLog.debug( "Given PropertySource is instanceof " + aPropSource.getClass().getName()
+ " and cannot be iterated" );
return result;
}
private static void addAll( Map<String, Object> aBase, Map<String, Object> aToBeAdded )
{
for (Entry<String, Object> entry : aToBeAdded.entrySet())
{
if ( aBase.containsKey( entry.getKey() ) )
{
continue;
}
aBase.put( entry.getKey(), entry.getValue() );
}
}
Note that the starting point is the ConfigurableEnvironment which is able to return the embedded PropertySources (the ConfigurableEnvironment is a direct descendant of Environment). You can autowire it by:
#Autowired
private ConfigurableEnvironment myEnv;
If you not using very special kinds of property sources (like JndiPropertySource, which is usually not used in spring autoconfiguration) you can retrieve all properties held in the environment.
The implementation relies on the iteration order which spring itself provides and takes the first found property, all later found properties with the same name are discarded. This should ensure the same behaviour as if the environment were asked directly for a property (returning the first found one).
Note also that the returned properties are not yet resolved if they contain aliases with the ${...} operator. If you want to have a particular key resolved you have to ask the Environment directly again:
myEnv.getProperty( key );
The original question hinted that it would be nice to be able to filter all the properties based on a prefix. I have just confirmed that this works as of Spring Boot 2.1.1.RELEASE, for Properties or Map<String,String>. I'm sure it's worked for while now. Interestingly, it does not work without the prefix = qualification, i.e. I do not know how to get the entire environment loaded into a map. As I said, this might actually be what OP wanted to begin with. The prefix and the following '.' will be stripped off, which might or might not be what one wants:
#ConfigurationProperties(prefix = "abc")
#Bean
public Properties getAsProperties() {
return new Properties();
}
#Bean
public MyService createService() {
Properties properties = getAsProperties();
return new MyService(properties);
}
Postscript: It is indeed possible, and shamefully easy, to get the entire environment. I don't know how this escaped me:
#ConfigurationProperties
#Bean
public Properties getProperties() {
return new Properties();
}
As this Spring's Jira ticket, it is an intentional design. But the following code works for me.
public static Map<String, Object> getAllKnownProperties(Environment env) {
Map<String, Object> rtn = new HashMap<>();
if (env instanceof ConfigurableEnvironment) {
for (PropertySource<?> propertySource : ((ConfigurableEnvironment) env).getPropertySources()) {
if (propertySource instanceof EnumerablePropertySource) {
for (String key : ((EnumerablePropertySource) propertySource).getPropertyNames()) {
rtn.put(key, propertySource.getProperty(key));
}
}
}
}
return rtn;
}
Spring won't allow to decouple via java.util.Properties from Spring Environment.
But Properties.load() still works in a Spring boot application:
Properties p = new Properties();
try (InputStream is = getClass().getResourceAsStream("/my.properties")) {
p.load(is);
}
The other answers have pointed out the solution for the majority of cases involving PropertySources, but none have mentioned that certain property sources are unable to be casted into useful types.
One such example is the property source for command line arguments. The class that is used is SimpleCommandLinePropertySource. This private class is returned by a public method, thus making it extremely tricky to access the data inside the object. I had to use reflection in order to read the data and eventually replace the property source.
If anyone out there has a better solution, I would really like to see it; however, this is the only hack I have gotten to work.
Working with Spring Boot 2, I needed to do something similar. Most of the answers above work fine, just beware that at various phases in the app lifecycles the results will be different.
For example, after a ApplicationEnvironmentPreparedEvent any properties inside application.properties are not present. However, after a ApplicationPreparedEvent event they are.
For Spring Boot, the accepted answer will overwrite duplicate properties with lower priority ones. This solution will collect the properties into a SortedMap and take only the highest priority duplicate properties.
final SortedMap<String, String> sortedMap = new TreeMap<>();
for (final PropertySource<?> propertySource : env.getPropertySources()) {
if (!(propertySource instanceof EnumerablePropertySource))
continue;
for (final String name : ((EnumerablePropertySource<?>) propertySource).getPropertyNames())
sortedMap.computeIfAbsent(name, propertySource::getProperty);
}
I though I'd add one more way. In my case I supply this to com.hazelcast.config.XmlConfigBuilder which only needs java.util.Properties to resolve some properties inside the Hazelcast XML configuration file, i.e. it only calls getProperty(String) method. So, this allowed me to do what I needed:
#RequiredArgsConstructor
public class SpringReadOnlyProperties extends Properties {
private final org.springframework.core.env.Environment delegate;
#Override
public String getProperty(String key) {
return delegate.getProperty(key);
}
#Override
public String getProperty(String key, String defaultValue) {
return delegate.getProperty(key, defaultValue);
}
#Override
public synchronized String toString() {
return getClass().getName() + "{" + delegate + "}";
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
SpringReadOnlyProperties that = (SpringReadOnlyProperties) o;
return delegate.equals(that.delegate);
}
#Override
public int hashCode() {
return Objects.hash(super.hashCode(), delegate);
}
private void throwException() {
throw new RuntimeException("This method is not supported");
}
//all methods below throw the exception
* override all methods *
}
P.S. I ended up not using this specifically for Hazelcast because it only resolves properties for XML file but not at runtime. Since I also use Spring, I decided to go with a custom org.springframework.cache.interceptor.AbstractCacheResolver#getCacheNames. This resolves properties for both situations, at least if you use properties in cache names.
To get ONLY properties, defined in my hibernate.properteies file:
#PropertySource(SomeClass.HIBERNATE_PROPERTIES)
public class SomeClass {
public static final String HIBERNATE_PROPERTIES = "hibernate.properties";
#Autowired
private Environment env;
public void someMethod() {
final Properties hibProps = asProperties(HIBERNATE_PROPERTIES);
}
private Properties asProperties(String fileName) {
return StreamSupport.stream(
((AbstractEnvironment) env).getPropertySources().spliterator(), false)
.filter(ps -> ps instanceof ResourcePropertySource)
.map(ps -> (ResourcePropertySource) ps)
.filter(rps -> rps.getName().contains(fileName))
.collect(
Properties::new,
(props, rps) -> props.putAll(rps.getSource()),
Properties::putAll);
}
}
A little helper to analyze the sources of a property, which sometimes drive me crazy . I used this discussion to write SpringConfigurableEnvironment.java on github.
It could be used in a test:
#SpringBootTest
public class SpringConfigurableEnvironmentTest {
#Autowired
private ConfigurableEnvironment springEnv;
#Test
public void testProperties() {
SpringConfigurableEnvironment properties = new SpringConfigurableEnvironment(springEnv);
SpringConfigurableEnvironment.PropertyInfo info = properties.get("profile.env");
assertEquals("default", properties.get(info.getValue());
assertEquals(
"Config resource 'class path resource [application.properties]' via location 'optional:classpath:/'",
info.getSourceList.get(0));
}
}
All answers above have pretty much covers everything, but be aware of overridden values from environment variables. They may have different key values.
For example, if a user override my.property[1].value using environment variable MY_PROPERTY[1]_VALUE, iterating through EnumerablePropertySources.getPropertyNames() would give you both my.property[1].value and MY_PROPERTY[1]_VALUE key values.
What even worse is that if my.property[1].value is not defined in applications.conf (or applications.yml), a MY_PROPERTY[1]_VALUE in environment variables would not give you my.property[1].value but only MY_PROPERTY[1]_VALUE key value from EnumerablePropertySources.getPropertyNames().
So it is developers' job to cover the those properties from environment variables. Unfortunately, there is no one-on-one mapping between environment variables schema vs the normal schema, see the source code of SystemEnvironmentPropertySource. For example, MY_PROPERTY[1]_VALUE could be either my.property[1].value or my-property[1].value

PropertySourcesPlaceholderConfigurer not registering with Environment in a SpringBoot Project

I am moving a working project from using SpringBoot command line arguments to reading properties from a file. Here are the involved portions of the #Configuration class:
#Configuration
class RemoteCommunication {
#Inject
StandardServletEnvironment env
#Bean
static PropertySourcesPlaceholderConfigurer placeholderConfigurer () {
// VERIFIED this is executing...
PropertySourcesPlaceholderConfigurer target = new PropertySourcesPlaceholderConfigurer()
// VERIFIED this files exists, is readable, is a valid properties file
target.setLocation (new FileSystemResource ('/Users/me/Desktop/mess.properties'))
// A Debugger does NOT show this property source in the inject Environment
target
}
#Bean // There are many of these for different services, only one shown here.
MedicalSorIdService medicalSorIdService () {
serviceInstantiator (MedicalSorIdService_EpicSoap, 'uri.sor.id.lookup.internal')
}
// HELPER METHODS...
private <T> T serviceInstantiator (final Class<T> classToInstantiate, final String propertyKeyPrimary) {
def value = retrieveSpringPropertyFromConfigurationParameter (propertyKeyPrimary)
classToInstantiate.newInstance (value)
}
private def retrieveSpringPropertyFromConfigurationParameter (String propertyKeyPrimary) {
// PROBLEM: the property is not found in the Environment
def value = env.getProperty (propertyKeyPrimary, '')
if (value.isEmpty ()) throw new IllegalStateException ('Missing configuration parameter: ' + "\"$propertyKeyPrimary\"")
value
}
Using #Value to inject the properties does work, however I'd rather work with the Environment directly if at all possible. If the settings are not in the Environment then I am not exactly sure where #Value is pulling them from...
env.getProperty() continues to work well when I pass in command line arguments specifying the properties though.
Any suggestions are welcome!
The issue here is the distinction between PropertySourcesPlaceholderConfigurer and StandardServletEnvironment, or Environment for simplicity.
The Environment is an object that backs the whole ApplicationContext and can resolve a bunch of properties (the Environment interface extends PropertyResolver). A ConfigurableEnvironment has a MutablePropertySources object which you can retrieve through getPropertySources(). This MutablePropertySources holds a LinkedList of PropertySource objects which are checked in order to resolve a requested property.
PropertySourcesPlaceholderConfigurer is a separate object with its own state. It holds its own MutablePropertySources object for resolving property placeholders. PropertySourcesPlaceholderConfigurer implements EnvironmentAware so when the ApplicationContext gets hold of it, it gives it its Environment object. The PropertySourcesPlaceholderConfigurer adds this Environment's MutablePropertySources to its own. It then also adds the various Resource objects you specified with setLocation() as additional properties. These Resource objects are not added to the Environment's MutablePropertySources and therefore aren't available with env.getProperty(String).
So you cannot get the properties loaded by the PropertySourcesPlaceholderConfigurer into the Environment directly. What you can do instead is add directly to the Environment's MutablePropertySouces. One way is with
#PostConstruct
public void setup() throws IOException {
Resource resource = new FileSystemResource("spring.properties"); // your file
Properties result = new Properties();
PropertiesLoaderUtils.fillProperties(result, resource);
env.getPropertySources().addLast(new PropertiesPropertySource("custom", result));
}
or simply (thanks #M.Deinum)
#PostConstruct
public void setup() throws IOException {
env.getPropertySources().addLast(new ResourcePropertySource("custom", "file:spring.properties")); // the name 'custom' can come from anywhere
}
Note that adding a #PropertySource has the same effect, ie. adding directly to the Environment, but you're doing it statically rather than dynamically.
In SpringBoot it's enough to use #EnableConfigurationProperties annotation - you don't need to setup PropertySourcesPlaceholderConfigurer.
Then on POJO you add annotation #ConfigurationProperties and Spring automatically injects your properties defined in application.properties.
You can also use YAML files - you just need to add proper dependency (like SnakeYaml) to classpath
You can find detailed example here: http://spring.io/blog/2013/10/30/empowering-your-apps-with-spring-boot-s-property-support
I achieved this during PropertySourcesPlaceholderConfigurer instantiation.
#Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurerBean(Environment env) {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
YamlPropertiesFactoryBean yamlFactorybean = new YamlPropertiesFactoryBean();
yamlFactorybean.setResources(determineResources(env));
PropertiesPropertySource yampProperties = new PropertiesPropertySource("yml", yamlFactorybean.getObject());
((AbstractEnvironment)env).getPropertySources().addLast(yampProperties);
propertySourcesPlaceholderConfigurer.setProperties(yamlFactorybean.getObject());
return propertySourcesPlaceholderConfigurer;
}
private static Resource[] determineResources(Environment env){
int numberOfActiveProfiles = env.getActiveProfiles().length;
ArrayList<Resource> properties = new ArrayList(numberOfActiveProfiles);
properties.add( new ClassPathResource("application.yml") );
for (String profile : env.getActiveProfiles()){
String yamlFile = "application-"+profile+".yml";
ClassPathResource props = new ClassPathResource(yamlFile);
if (!props.exists()){
log.info("Configuration file {} for profile {} does not exist");
continue;
}
properties.add(props);
}
if (log.isDebugEnabled())
log.debug("Populating application context with properties files: {}", properties);
return properties.toArray(new Resource[properties.size()]);
}
Maybe all you need is to set -Dspring.config.location=... (alternatively SPRING_CONFIG_LOCATION as an env var)? That has the effect of adding an additional config file to the default path for the app at runtime which takes precedence over the normal application.properties? See howto docs for details.

Resources