Spring 4 PropertySourcesPlaceholderConfigurer doesn't resolve ${..} placeholders across modules - java

I have two modules in my applications:
core
web
The core module contains the following property place-holder configuration in the spring/applicationContext-core.xml context:
<bean id="coreProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<property name="locations">
<list>
<value>classpath:/properties/*.properties</value>
<value>classpath:/profiles/${build.profile.id}/properties/*.properties</value>
<value>file:${ui.home}/profiles/${build.profile.id}/properties/*.properties</value>
</list>
</property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
<property name="ignoreResourceNotFound" value="false"/>
<property name="properties" ref="coreProperties" />
</bean>
<bean id="propertySourcesPlaceholderConfigurer" class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="ignoreUnresolvablePlaceholders" value="true"/>
</bean>
Considering I have the following property:
resource.suffix=.min
If I inject this value in a core #Component:
#Value("${resource.suffix}")
private String resourceSuffix;
The property is properly resolved .
But, if I add the same configuration in a bean inside the web module, which simply loads the core configurations as well:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/servlet-context.xml /WEB-INF/application-security.xml
classpath*:/spring/applicationContext-core.xml</param-value>
</context-param>
then the property is not resolved and the resourceSuffix value is set to the following String literal value ${resource.suffix.
What am I missing?

I believe this has to do with how spring works with pre/post processors.
Basically you can have duplicated definition or use a different mechanism for loading properties.
As much as I know before spring 3.1 duplication is the only way.
More on http://www.baeldung.com/2012/02/06/properties-with-spring/#parent-child

Related

Using Spring Integration SpEl inside value tag

I am trying to set the properties file inside a class that extends the PropertyPlaceholderConfigurer based upon the environment (local, dev, ref, qa, prod)
My folder structure looks like the following.
properties
environment.properties
server-local.properties
server-ref.properties
server-prod.properties
email-local.properties
email-ref.properties
email-prod.properties
cache-local.properties
cache-ref.properties
cache-prod.properties
The environment.properties has a property
environment.stage=local (or whatever env this is)
My Spring Integration context statements look something like this:
<context:property-placeholder location="classpath:properties/*.properties" />
<bean id="propertyPlaceholder" class="com.turner.bvi.BviPropertiesUtil">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>classpath:properties/environment.properties</value>
<value>classpath:properties/*-${environment.stage}.properties</value>
</list>
</property>
</bean>
What I want to do is have only the properties file from the particular environment stage load (be it local, ref, prod .... etc.). How do I get just this second set of properties files to load based upon environment.stage?
Thanks for the help in advance.
You can use Spring profiles for this, it would be something like this:
<context:property-placeholder location="classpath:properties/*.properties" />
<beans profile="local">
<bean id="propertyPlaceholder" class="com.turner.bvi.BviPropertiesUtil">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>classpath:properties/environment.properties</value>
<value>classpath:properties/*-local.properties</value>
</list>
</property>
</bean>
</beans>
<beans profile="dev">
<bean id="propertyPlaceholder" class="com.turner.bvi.BviPropertiesUtil">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>classpath:properties/environment.properties</value>
<value>classpath:properties/*-local.properties</value>
</list>
</property>
</bean>
</beans>
...
Setting an environment variable can be done using spring_profiles_active (or spring_profiles_default). In Unix try exporting SPRING_PROFILES_DEFAULT=local
You can alternativerly use the JVM parameters like -Dspring.profiles.active=local
In case you need to maintain the environment variables as they are, you can implement a custom ActiveProfilesResolver as described in here: Spring Profiles: Simple example of ActiveProfilesResolver?
Thank you all for your help. I was able to take snippets of all your suggestions and come up with a solution using "environmentProperties" in the Spring context.
The problem with trying to use a in the context was that it had not been set at the time MY class was trying to resolve the ${environment.stage}... Or at least that is what I gathered from searching other posts.
If my properties file structure looks like this:
properties
environment.properties
server-local.properties
server-ref.properties
server-prod.properties
email-local.properties
email-ref.properties
email-prod.properties
cache-local.properties
cache-ref.properties
cache-prod.properties
I was able to set the Environment property 'env' to the correct value through Chef or through a Docker container and use the following code.
<!-- Register the properties file location -->
<context:property-placeholder location="classpath:properties/*.properties" />
<bean id="propertyPlaceholder" class="com.turner.bvi.BviPropertiesUtil">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>classpath:properties/environment.properties</value>
<value>classpath:properties/*-#{systemEnvironment['env']}.properties</value>
</list>
</property>
</bean>
I could have put each of the property sets under its own directory and had
<value>classpath:properties/#{systemEnvironment['env']}/*.properties</value>
Again, thank you all for your help.

Spring loading application.properties based on tomcat servlet context definition

I need to have a development and production settings for our spring project. I understand that you can use profiles for spring but that is not something that we can do.
What I want to do is place on the development environment a test-application.properties file and on production a prod-application.properties file. In the tomcat context definition we sent the following:
<Context>
<context-param>
<param-name>properties_location</param-name>
<param-value>file:C:\Users\Bill\test-application.properties</param-value>
</context-param>
</Context>
And we can have the value changed for the production servers. In the spring config we have something like this:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>${properties_location}</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="false" />
</bean>
But we keep getting errors like:
org.springframework.beans.factory.BeanInitializationException: Could
not load properties; nested exception is
java.io.FileNotFoundException: Could not open ServletContext resource
[/${properties_location}]
Any ideas on how to solve?
One feature of PropertyPlaceholder is that you can define multiple resource locations.
So for example you can define your-production-config.properties along with file:C:/Users/${user.name}/test-application.properties
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:your-production-config.properties</value>
<value>file:C:/Users/${user.name}/test-application.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true"/>
<property name="ignoreResourceNotFound" value="true"/>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
</bean>
for production you need to place prod configuration into classpath somewhere(really not important where exactly, just classpath) - for local env you can use convension like this file:C:/Users/${user.name}/test-application.properties
<context:property-placeholder location="file:${catalina.home}/conf/myFirst.properties" ignore-unresolvable="true" />
<context:property-placeholder location="classpath:second.properties" ignore-unresolvable="true" />
I do it like above. The catalina.home variable allows the properties file to be lcoated in the tomcat home conf directory.
I ended up solving it by not using context params. Instead we have defined
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:application.properties</value>
<value>file:C:\Users\Bill\prod-application.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="ignoreResourceNotFound" value="true"/>
</bean>
This way tries to load both files. On test servers we do not have the prod file so it is not loaded. On prod server the prod-application.properties file exists and overrides the test which is in the classpath. Cumbersome but works!
Personally, try to avoid specify locations. I think best thing for you is to use JNDI to achieve this.
In tomcat/conf/server.xml
<Resource name="jdbc/prod" auth="Container"
type="javax.sql.DataSource" driverClassName="${database.driverClassName}"
url="${database.url}"
username="${database.username}" password="${database.password}"
maxActive="20" maxIdle="10"
maxWait="-1"/>
and In tomcat catalina.properties (If using Oracle XE otherwise change it accordingly):
database.driverClassName=oracle.jdbc.driver.OracleDriver
database.url=jdbc:oracle:thin:#//localhost:1521/XE
database.username=user
database.password=password
In your application create properties file in your classpath named jdbc.properties and put followings (If using Oracle XE otherwise change it accordingly)
jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
jdbc.url=jdbc:oracle:thin:user/password#//localhost:1521/XE
then In Spring applicationContext.xml
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/prod" />
<property name="defaultObject" ref="dataSourceFallback" />
</bean>
<bean id="dataSourceFallback" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="poolPreparedStatements">
<value>true</value>
</property>
<property name="maxActive">
<value>4</value>
</property>
<property name="maxIdle">
<value>1</value>
</property>
</bean>
use :
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations">
<list>
<value>C:/Users/Bill/test-application.properties</value>
</list>
</property>
<property name="ignoreUnresolvablePlaceholders" value="false" />
</bean>
Remove below code from web.xml
<Context>
<context-param>
<param-name>properties_location</param-name>
<param-value>file:C:\Users\Bill\test-application.properties</param-value>
</context-param>
</Context>
if you are using Tcserver and server.xml to configure Resources like database,queues etc you can using the com.springsource.tcserver.properties.SystemProperties
Delcare this listener in server.xml like below
<Listener className="com.springsource.tcserver.properties.SystemProperties"
file.1="${catalina.base}/conf/password.properties"
file.2="${catalina.base}/conf/server.properties"
immutable="false"
trigger="now"/>
Now you can externalize the properties to the two files password.properties and server.properties.

Initializing Log4J with Spring?

I have a web app that uses Spring's Log4jConfigurer class to initialize my Log4J log factory. Basically it initializes Log4J with a config file that is off the class path.
Here is the config:
<bean id="log4jInitializer" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" depends-on="sbeHome">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>#{ MyAppHome + '/conf/log4j.xml'}</value>
</list>
</property>
</bean>
However I get this error at application startup:
log4j:WARN No appenders could be found for logger
and tons of Spring application context initialization messages are printed to the console. I think this is because Spring is doing work to initialize my application before it has a chance to initialize my logger. In case it matters, I am using SLF4J on top of Log4J.
Is there some way I can get my Log4jConfigurer to be the first bean initialized? or is there some other way to solve this?
You could configure your Log4j listener in the web.xml instead of the spring-context.xml
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>/WEB-INF/classes/log4j.web.properties</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
So it is up before Spring starts.
Our standalone application required an SMTPAppender where the email config already exists in a spring config file and we didn't want that to be duplicated in the log4j.properties.
I put the following together to add an extra appender using spring.
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetObject">
<bean factory-method="getRootLogger"
class="org.apache.log4j.Logger" />
</property>
<property name="targetMethod">
<value>addAppender</value>
</property>
<property name="arguments">
<list>
<bean init-method="activateOptions"
class="org.apache.log4j.net.SMTPAppender">
<property name="SMTPHost" ref="mailServer" />
<property name="from" ref="mailFrom" />
<property name="to" ref="mailTo" />
<property name="subject" ref="mailSubject" />
<property value="10" name="bufferSize" />
<property name="layout">
<bean class="org.apache.log4j.PatternLayout">
<constructor-arg>
<value>%d, [%5p] [%t] [%c] - %m%n</value>
</constructor-arg>
</bean>
</property>
<property name="threshold">
<bean class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean"
id="org.apache.log4j.Priority.ERROR" />
</property>
</bean>
</list>
</property>
</bean>
We also have a log4j.properties file on the classpath which details our regular FileAppenders.
I realise this may be overkill for what you require :)
Rather than configuring log4j yourself in code, why not just point log4j at your (custom) configuration file's location by adding
-Dlog4j.configuration=.../conf/log4j.xml
to your server's startup properties?
Even better, just move log4j.xml to the default location - on the classpath - and let log4j configure itself automatically.
You can use classpath instead of hardcoded path. It works for me
<bean id="log4jInitializer" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" depends-on="sbeHome">
<property name="targetClass" value="org.springframework.util.Log4jConfigurer" />
<property name="targetMethod" value="initLogging" />
<property name="arguments">
<list>
<value>classpath:/conf/log4j.xml</value>
</list>
</property>
</bean>
If you are using Jetty you can add extra classpaths on a per application basis:
http://wiki.eclipse.org/Jetty/Reference/Jetty_Classloading#Adding_Extra_Classpaths_to_Jetty
This will allow you to load your log4 properties in a standard manner (from the classpath:)
in web.xml:
<listener>
<listener-class>org.springframework.web.util.Log4jWebConfigurer</listener-class>
</listener>
<context-param>
<param-name>log4jConfigLocation</param-name>
<param-value>classpath:${project.artifactId}-log4j.properties</param-value>
</context-param>
in jetty-web.xml:
<Set name="extraClasspath">
<SystemProperty name="config.home" default="."/>/contexts/log4j
</Set>

how to read System environment variable in Spring applicationContext

How to read the system environment variable in the application context?
I want something like :
<util:properties id="dbProperties"
location="classpath:config_DEV/db.properties" />
or
<util:properties id="dbProperties"
location="classpath:config_QA/db.properties" />
depending on the environement.
Can I have something like this in my application Context?
<util:properties id="dbProperties"
location="classpath:config_${systemProperties.env}/db.properties" />
where the actual val is set based on the SYSTEM ENVIRONMENT VARIABLE
I'm using Spring 3.0
You are close :o)
Spring 3.0 adds Spring Expression Language.
You can use
<util:properties id="dbProperties"
location="classpath:config_#{systemProperties['env']}/db.properties" />
Combined with java ... -Denv=QA should solve your problem.
Note also a comment by #yiling:
In order to access system environment variable, that is OS level
variables as amoe commented, we can simply use "systemEnvironment"
instead of "systemProperties" in that EL. Like
#{systemEnvironment['ENV_VARIABLE_NAME']}
Nowadays you can put
#Autowired
private Environment environment;
in your #Component, #Bean, etc., and then access the properties through the Environment class:
environment.getProperty("myProp");
For a single property in a #Bean
#Value("${my.another.property:123}") // value after ':' is the default
Integer property;
Another way are the handy #ConfigurationProperties beans:
#ConfigurationProperties(prefix="my.properties.prefix")
public class MyProperties {
// value from my.properties.prefix.myProperty will be bound to this variable
String myProperty;
// and this will even throw a startup exception if the property is not found
#javax.validation.constraints.NotNull
String myRequiredProperty;
//getters
}
#Component
public class MyOtherBean {
#Autowired
MyProperties myProperties;
}
Note: Just remember to restart eclipse after setting a new environment variable
Check this article. It gives you several ways to do this, via the PropertyPlaceholderConfigurer which supports external properties (via the systemPropertiesMode property).
Yes, you can do <property name="defaultLocale" value="#{ systemProperties['user.region']}"/> for instance.
The variable systemProperties is predefined, see 6.4.1 XML based configuration.
In your bean definition, make sure to include "searchSystemEnvironment" and set it to "true". And if you're using it to build a path to a file, specify it as a file:/// url.
So for example, if you have a config file located in
/testapp/config/my.app.config.properties
then set an environment variable like so:
MY_ENV_VAR_PATH=/testapp/config
and your app can load the file using a bean definition like this:
e.g.
<bean class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
<property name="searchContextAttributes" value="true" />
<property name="contextOverride" value="true" />
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>file:///${MY_ENV_VAR_PATH}/my.app.config.properties</value>
</list>
</property>
</bean>
Using Spring EL you can eis example write as follows
<bean id="myBean" class="path.to.my.BeanClass">
<!-- can be overridden with -Dtest.target.host=http://whatever.com -->
<constructor-arg value="#{systemProperties['test.target.host'] ?: 'http://localhost:18888'}"/>
</bean>
For my use case, I needed to access just the system properties, but provide default values in case they are undefined.
This is how you do it:
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
</bean>
<bean id="myBean" class="path.to.my.BeanClass">
<!-- can be overridden with -Dtest.target.host=http://whatever.com -->
<constructor-arg value="${test.target.host:http://localhost:18888}"/>
</bean>
Declare the property place holder as follows
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>file:///path.to.your.app.config.properties</value>
</list>
</property>
</bean>
Then lets say you want to read System.property("java.io.tmpdir") for your Tomcat bean or any bean then add following in your properties file:
tomcat.tmp.dir=${java.io.tmpdir}
This is how you do it:
<bean id="systemPrereqs" class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" scope="prototype">
<property name="targetObject" value="#{#systemProperties}" />
<property name="targetMethod" value="putAll" />
<property name="arguments">
<util:properties>
<prop key="deployment.env">dev</prop>
</util:properties>
</property>
</bean>
But remember spring gets loaded first and then it will load this bean MethodInvokingFactoryBean. So if you are trying to use this for your test case then make sure that you use depends-on. For e.g. in this case
In case you are using it for your main class better to set this property using your pom.xml as
<systemProperty>
<name>deployment.env</name>
<value>dev</value>
</systemProperty>
You can mention your variable attributes in a property file and define environment specific property files like local.properties, production.propertied etc.
Now based on the environment, one of these property file can be read in one the listeners invoked at startup, like the ServletContextListener.
The property file will contain the the environment specific values for various keys.
Sample "local.propeties"
db.logsDataSource.url=jdbc:mysql://localhost:3306/logs
db.logsDataSource.username=root
db.logsDataSource.password=root
db.dataSource.url=jdbc:mysql://localhost:3306/main
db.dataSource.username=root
db.dataSource.password=root
Sample "production.properties"
db.logsDataSource.url=jdbc:mariadb://111.111.111.111:3306/logs
db.logsDataSource.username=admin
db.logsDataSource.password=xyzqer
db.dataSource.url=jdbc:mysql://111.111.111.111:3306/carsinfo
db.dataSource.username=admin
db.dataSource.password=safasf#mn
For using these properties file, you can make use of REsource as mentioned below
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:"+System.getenv("SERVER_TYPE")+"DB.properties");
configurer.setLocation(resource);
configurer.postProcessBeanFactory(beanFactory);
SERVER_TYPE can be defined as the environment variable with appropriate values for local and production environment.
With these changes the appplicationContext.xml will have the following changes
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="${db.dataSource.url}" />
<property name="username" value="${db.dataSource.username}" />
<property name="password" value="${db.dataSource.password}" />
Hope this helps .
Thanks to #Yiling. That was a hint.
<bean id="propertyConfigurer"
class="org.springframework.web.context.support.ServletContextPropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="searchSystemEnvironment" value="true" />
<property name="locations">
<list>
<value>file:#{systemEnvironment['FILE_PATH']}/first.properties</value>
<value>file:#{systemEnvironment['FILE_PATH']}/second.properties</value>
<value>file:#{systemEnvironment['FILE_PATH']}/third.properties</value>
</list>
</property>
</bean>
After this, you should have one environment variable named 'FILE_PATH'. Make sure you restart your terminal/IDE after creating that environment variable.
Updated version (2020).
Use System.getenv("ENV_VARIABLE")

property-placeholder location from another property

I need to load some properties into a Spring context from a location that I don't know until the program runs.
So I thought that if I had a PropertyPlaceholderConfigurer with no locations it would read in my.location from the system properties and then I could use that location in a context:property-placeholder
Like this
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"/>
<context:property-placeholder location="${my.location}"/>
but this doesn't work and nor does location="classpath:${my.location}"
Paul
You can do this with a slightly different approach. Here is how we configure it. I load default properties and then overrided them with properties from a configurable location. This works very well for me.
<bean id="propertyPlaceholderConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="locations">
<list>
<value>classpath:site/properties/default/placeholder.properties
</value>
<value>classpath:site/properties/${env.name}/placeholder.properties
</value>
</list>
</property>
</bean>
The problem here is that you're trying to configure a property place holder using property placeholder syntax :) It's a bit of a chicken-and-egg situation - spring can't resolve your ${my.location} placeholder until it's configured the property-placeholder.
This isn't satisfactory, but you could bodge it by using more explicit syntax:
<bean class="org.springframework.beans.factory.config.PropertyPlaceHolderConfigurer">
<property name="location">
<bean class="java.lang.System" factory-method="getenv">
<constructor-arg value="my.location"/>
</bean>
</property>
</bean>

Categories