I wish to expose a Properties Spring bean whose values have been expanded via the typical property expansion mechanism. I'm using Spring 3.1. Let me digress.
Given the following properties file:
server.host=myhost.com
service.url=http://${server.host}/some/endpoint
And this portion of Spring XML config file:
<bean id="appProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:default.properties</value>
</list>
</property>
</bean>
<context:property-placeholder properties-ref="appProperties" />
I can write the following working code:
#Component
public class MyComponent {
#Autowired
#Qualifier("appProperties")
private Properties appProperties;
#Value("${service.url}")
private String serviceUrl;
// remainder omitted
}
The only problem is that if I obtain the service.url value from appProperties I get http://${server.host}/some/endpoint - ie the value is unexpanded. However, if I get the value of service.url from serviceUrl, the value has been expanded: http://myhost.com/some/endpoint.
Does anyone know of a good way to expose a Properties instance as a Spring bean whose values have been expanded?
Alternatively, if anyone can point me to a Spring bean (must be Spring 3.1) that will do the expansion for me, I'll accept this too! (Interestingly, if you manually pull the property values from the Environment or PropertySource you'll find that these too are unexpanded.)
Thanks,
Muel.
I'm pretty late to this, but it's been viewed enough times that i figured it warranted a quick response. What you're seeing is an artifact of how Spring handles property expansion. When the need for property expansion is found, only previously loaded sources are checked, not the currently loading property source. When the file loads, there are no previous sources, so ${server.host} does not expand. When you later reference ${server.url} via the #Value annotation, the property file source is loaded and can be searched as you expected. This is why the #Value annotation gets full expansion but the result queried from the property file does not.
Related
Am really new to spring, so please don't get angry and bear with me for a while.
I'm trying to understand how application.properties or any other external config file values gets associated with the places where config file's keys (value="${log4j.configuration}") are mentioned to consume their values from file itself.
For example below is spring bean xml file:
<bean id = "propertiesToBeTaken"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath*:application.properties</value>
<value>classpath*:*keys.properties</value>
</list>
</property>
</bean>
<bean id="log4jLoader" class="my.loader.Log4jLoader">
<property name="log4jCongif" value="${log4j.configuration}" />
</bean>
As you can see, propertiesToBeTaken is an instance of class org.springframework.beans.factory.config.PropertyPlaceholderConfigurer.
And in second case ,log4jLoader is an instance of class my.loader.Log4jLoader, where log4j.configuration key's value is assigned to Log4jLoader class's instance variable log4jCongif.
My questions are below:
There is no locations variable in class PropertyPlaceholderConfigurer, then what is locations here and from where it came ? How can I relate locations to class's instance propertiesToBeTaken?
If lets say, application.properties key value pairs gets loaded into memory, then who or I mean which method loads that ? And even if it gets loaded then how those key value pairs of application.properties file are accessible to Log4jLoader's instance variables...?
Last but I think it could very stupid question, In class log4jLoader class, there is a setLog4jConfig(String log4jpropertiesLocation), but I really cant see who is calling this method. Just correct me here, Its not true, that In spring what ever the beans of object we have created, calls automatically class's instance methods. RIGHT ? I need to search more in code. CORRECT ?
Please put some light on my doubts here. Read about this alot on spring docs and online as well, but could not understand. Really want to understand how it's happening.
Thanks
Answers to your questions:
locations in bean config XML is referring to the setLocations method of PropertiesLoaderSupport class which Set locations of properties files to be loaded. PropertiesLoaderSupport is parent class of PropertyPlaceholderConfigurer in multi-level inheritance. See Official Document
When PropertyPlaceholderConfigurer gets instantiated by Spring IoC, these properties are set in the bean, now any other bean in the config XML asks for property values using ${key.name}, spring injects the value from the bean having value for this key. In your case Log4jLoader requires ${log4j.configuration} so it's value will be injected by PropertyPlaceholderConfigurer or it's any parent class.
setLog4jConfig spring calls this method when it has to give value to one of the property of the class Log4jLoader. In your case because of <property name="log4jCongif" value="${log4j.configuration}" /> configuration spring will call setter method of the property log4jCongif to inject the value. Yes spring can call instance methods (specially setter) as per the configuration.
I have used the following ways to get the values from the properties. But I would like to know which one of these is the best to use to follow the coding standard? Also, are there any other ways that we can get the values from the properties file in Spring?
PropertySourcesPlaceholderConfigurer
getEnvironment() from the Spring's Application Context
Spring EL #Value
Along with the other configuration classes (ApplicationConfiguration etc.) I create a class with the annotation #Service and here I have the following fields to access the properties in my file:
#Service
public class Properties (){
#Value("${com.something.user.property}")
private String property;
public String getProperty (){ return this.property; }
}
Then I can autowire the class and get the properties from my properties file
The answer is,
it depends.
If the properties are configuration values,
then configure a propertyConfigurer
(below is an example for a Spring xml configuration file).
<bean id="propertyConfigurer"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:configuration.properties</value>
<value>classpath:configuration.overrides.properties</value>
</list>
</property>
</bean>
When configured this way,
the properties from the last file found override those found earler
(in the locations list).
This allows you to ship the standard configuration.properties file bundled in the war file and store a configuration.overrides.properties at each installation location to account for installation system differences.
Once you have a propertyConfigurer,
annotate your classes using the #Value annotation.
Here is an example:
#Value("${some.configuration.value}")
private String someConfigurationValue;
It is not required to cluster the configuration values into one class,
but doing so makes it easier to find where the values are used.
#Value will be the simple and easy way to use, as it will inject value from property file to your field.
Both the older PropertyPlaceholderConfigurer and the new PropertySourcesPlaceholderConfigurer added in Spring 3.1 resolve ${…} placeholders within bean definition property values and #Value annotations.
unlike getEnvironment
using property-placeholder will not expose the properties to the
Spring Environment – this means that retrieving the value like this
will not work – it will return null
when you are using <context:property-placeholder location="classpath:foo.properties" /> and you use env.getProperty(key); it will always return null.
see this post for the problem using getEnvironment : Expose <property-placeholder> properties to the Spring Environment
Moreover, in Spring Boot you can use #ConfigurationProperties to define your own properties with hierarchical and type-safe in application.properties. and you don't need to put #Value for every field.
#ConfigurationProperties(prefix = "database")
public class Database {
String url;
String username;
String password;
// standard getters and setters
}
in application.properties:
database.url=jdbc:postgresql:/localhost:5432/instance
database.username=foo
database.password=bar
Quote from : properties with spring
I have a Spring 3.1 application. Let's say it has an XML with the following content:
<context:property-placeholder location="classpath:somename.properties" />
<context:property-placeholder location="classpath:xxx.properties" />
I would like some.properties to be always loaded (let's assume it exists), but the xxx part of the second place holder to be replaced by some name depending on the active profile. I've tried with this:
<beans profile="xx1">
<context:property-placeholder location="classpath:xx1.properties" />
</beans>
<beans profile="xx2">
<context:property-placeholder location="classpath:xx2.properties" />
</beans>
Also, both files have properties with the same key but different value.
But it didn't work as some later bean that has a placeholder for one property whose key is defined in xx1.properties (and xx2.properties) makes Spring complain that the key is not found in the application context.
You can do:
<context:property-placeholder location="classpath:${spring.profiles.active}.properties" />
It works fine, but is perhaps not adapted when using multiple profiles in the same time.
When declaring 2 property placeholders, if the 1st one does not contain all the applications keys, you should put the attribute ignoring unresolvable = true, so that the 2nd placeholder can be used.
I'm not sure if it is what you want to do, it may if you want both xx1 and xx2 profiles be active in the same time.
Note that declaring 2 propertyplaceholders like that make them independant, and in the declaration of xx2.properties, you can't reuse the values of xx1.properties.
If you need something more advanced, you can register your PropertySources on application startup.
web.xml
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.xxx.core.spring.properties.PropertySourcesApplicationContextInitializer</param-value>
</context-param>
file you create:
public class PropertySourcesApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static final Logger LOGGER = LoggerFactory.getLogger(PropertySourcesApplicationContextInitializer.class);
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
LOGGER.info("Adding some additional property sources");
String profile = System.getProperty("spring.profiles.active");
// ... Add property sources according to selected spring profile
// (note there already are some property sources registered, system properties etc)
applicationContext.getEnvironment().getPropertySources().addLast(myPropertySource);
}
}
Once you've done it you just need to add in your context:
<context:property-placeholder/>
Imho it's the best way to deal with spring properties, because you do not declare local properties everywhere anymore, you have a programmatic control of what is happening, and property source xx1 values can be used in xx2.properties.
At work we are using it and it works nicely. We register 3 additional property sources:
- Infrastructure: provided by Puppet
- Profile: a different property loaded according to the profile.
- Common: contains values as default, when all profiles share the same value etc...
I have decided to submit and answer to this as it has not yet been accepted. It may not be what you are looking for specifically but it works for me. Also note that i am using the new annotation driven configuration however it can be ported to the xml config.
I have a properties file for each environment(dev.properties, test.properties etc)
I then have a RootConfig class that is the class that is used for all the configuration. All that this class has in it is two annotations: #Configuration and #ComponentScan(basePackageClasses=RootConfig.class).
This tells it to scan for anything in the same package as it.
There is then a Configuration Containing all my normal configuration sitting wherever. There is also a configuration for each environment in the same package as the root configuration class above.
The environment specific configurations are simply marker classes that have the following annotations to point it to the environment specific properties files:
#Configuration
#PropertySource("classpath:dev.properties")
#Import(NormalConfig.class)
#Profile("dev")
The import tells it to bring in the normal config class. But when it gets in there it will have the environment specific properties set.
I am using Spring Boot.
I declared properties in an external file outside the classpath.
I added that to one of my XML files:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>file:///d:/etc/services/pushExecuterService/pushExecuterServices.properties</value>
</list>
</property>
</bean>
However, I still get this error:
Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'configuration.serviceId' in string value "${configuration.serviceId}"
at org.springframework.util.PropertyPlaceholderHelper.parseStringValue(PropertyPlaceholderHelper.java:174)
I added a breakpoint in the PropertiesLoaderSupport class at this method:
public void setLocations(Resource... locations) {
this.locations = locations;
}
I paid attention that this method invoked multiple times and in one of them I noticed the locations param populated with:
URL [file:/d:/etc/services/pushExecuterService/pushExecuterServices.properties]
However, I am still getting this error.
I double checked in my project and I dont have any additional PropertyPlaceholderConfigurer beans(didnt check on external dependencies)
I ran my app with hard code the params inside the xml I can see within spring-boot logs:
2015-01-05 18:56:52.902 INFO 7016 --- [ main]
o.s.b.f.c.PropertyPlaceholderConfigurer: Loading properties file from
URL [file:/d:/etc/services/pushExecuterService/pushExecuterServices.properties]`
So I am not sure what's happening. any leads?
thank you.
Spring Boot favors Java-based configuration. In order to add configuration properties, we can use #PropertySource annotation together with #Configuration annotation.
The properties can be stored in any file. Property values can be injected directly into beans using the #Value annotation:
#Configuration
#PropertySource("classpath:mail.properties")
public class MailConfiguration {
#Value("${mail.protocol}")
private String protocol;
#Value("${mail.host}")
private String host;
}
#PropertySource's value attribute indicate the resource location(s) of the properties file to be loaded. For example, "classpath:/com/myco/app.properties" or "file:/path/to/file"
But Spring Boot provides an alternative method of working with properties that allows strongly typed beans to govern and validate the configuration of your application: #ConfigurationProperties
See this blog post with an example of using #ConfigurationProperties: http://blog.codeleak.pl/2014/09/using-configurationproperties-in-spring.html
For the #PropertySource example you can check this article: http://blog.codeleak.pl/2014/09/testing-mail-code-in-spring-boot.html
I have a BeanDefinitionDecorator that makes modifications to properties that a user would set on a bean. It works fine; except if the bean is using placeholders. I am trying to find a strategy to modify those values while still have access to the original value at runtime. An example of what this would look like in XML:
<bean id="bean">
<property name="jdbcUrl" value="${jdbc.url}" />
<d:spyDecorator />
</bean>
I know that user would be writing the jdbcUrl property as "jdbc:myDatabase". What I want to do is change their property to "jdbc:spy:myDatabase". This is easy if they are just using string literals for the property value, but if they are using property placeholders I am not sure how to change the value -- because I need the original value in order to supply the new value. They key is to keep the property rewriting transparent to the user.
Are there any possible solutions for this?
I think your namespace handler can register a BeanFactoryPostProcessor (implementing Orderer with order = Integer.MAX_VALUE to be the last post processor applied). Then your BeanDefinitionDecorator will register the beans being decorated for processing with that post processor (implement it in the post processor somehow), and post processor will apply the actual property modification to that beans.
You can use PropertyPlaceholderConfigurer to substitute property values for placeholders in bean properties, aliases, and other places. Note that the replacements happen AFTER the bean definitions have been loaded, so this mechanism does not apply to <import> elements
For example:
...
<bean id="ppc"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:build.properties</value>
<value>classpath:default-emmet-substitution.properties</value>
<value>classpath:default-danno-substitution.properties</value>
<value>classpath:default-dannotate-substitution.properties</value>
<value>classpath:substitution.properties</value>
</list>
</property>
</bean>
...
For more information refer to this section of the Spring Framework docs.
EDIT - I guess from your comment you are already familiar with how placeholder replacement works, and are using PropertyPlaceholderConfigurer to do the replacements. So now you need to choose between these strategies, or some combination:
Do the placeholder replacements yourself in your custom BeanDefinitionDecorator. That would work, though you'd be duplicating a lot of code.
Have the custom BeanDefinitionDecorator modify the placeholder names to different ones that will pull in different values; e.g. "${jdbc.url}" becomes "${spy.jdbc.url}".
Extend the PropertyPlaceholderConfigurer class to modify the substituted property values; i.e. override convertProperty or convertProperties. That has the potential problem that all placeholders will get the modified values ... not just the ones in beans that you have decorated.
Create a new PropertyResourceConfigurer class to substitute different property values depending on the context. Essentially, the processProperties needs to work like the method does in a PropertyPlaceholderConfigurer, but do something different if it sees bean properties or whatever that tell it to do the "spy" substitution.
A combination of 2) and 3) looks the most promising.