I am working on a project that requires me to take environment variables or system properties within a java spring application and modify them before they are injected into beans. The modification step is key for this application to work.
My current approach to this is to set the variables as system environment variables and then use a custom placeholder configurer to access the aforementioned variables and create new properties from them that the beans can access. There is a perfect tutorial for this (except it uses databases).
I have a POC using this approach working fine, but I think there might be an easier solution out there. Perhaps there is an approach to extend the default placeholder configurer to "hook in" custom code to do the necessary modifications for all properties in the entire application. Maybe there is a way to run code immediately after properties are gathered and before data is injected into beans.
Does spring provide an easier way to do this?
Thanks for your time
Simply put, the easiest way to accomplish this is to follow the directions under the section "Manipulating property sources in a web application" in the spring documentation for property management.
In the end, you reference a custom class from a web.xml through a context-param tag:
<context-param>
<param-name>contextInitializerClasses</param-name>
<param-value>com.some.something.PropertyResolver</param-value>
</context-param>
This forces spring to load this code before any beans are initialized. Then your class can do something like this:
public class PropertyResolver implements ApplicationContextInitializer<ConfigurableWebApplicationContext>{
#Override
public void initialize(ConfigurableWebApplicationContext ctx) {
Map<String, Object> modifiedValues = new HashMap<>();
MutablePropertySources propertySources = ctx.getEnvironment().getPropertySources();
propertySources.forEach(propertySource -> {
String propertySourceName = propertySource.getName();
if (propertySource instanceof MapPropertySource) {
Arrays.stream(((EnumerablePropertySource) propertySource).getPropertyNames())
.forEach(propName -> {
String propValue = (String) propertySource.getProperty(propName);
// do something
});
}
});
}
}
Related
We are loading properties from an external file using #PropertySources. Now I want to enable/disable #Aspect based on a property. I tried using #ConditionalOnExpression which didn't work. I tried the same by creating a bean of propertyplaceholderconfig. Even in the same case, it didn't work. Then I tried #profile which also didn't work initially.
What I Figured out is that these variables are not initialized at the starting when propertysource or propertyplaceholder bean is used at startup. Some variables are always ignored like (logging.file). But #Value works fine. In order to set these variables, I've to pass them as JVM parameters.
So my questions are:
1. How can I make spring to always read specified property files at startup and respect all of them?
2. Which is the best way to enable/disable #Aspect. Using #profile or #ConditionalOnExpression or something else?
Currently, we are setting logging.file in the main method since this also behaves the same way. But you guys know that it's not the proper way as I may end up adding the properties one by one like this. I want to put all the properties into external files such that spring reads those files and sets its properties.
Our properties structure:
common.properties #This has all common properties
service.properties #Property specific to a service. This will also contain existing property from common.properties which will be overridden.
I understand that I can use profiles. But, we want to keep the properties outside such you need to restart service if you are changing the properties. I also don't want to pass the variables as JVM parameters then I've to pass most of the variables in this way. Passing -Dspring.config.location is also difficult as common.properties and service.properties are used and 'service.properties' filename varies for each service.
sample codes:
Mainclass:
#PropertySources({
#PropertySource(value = "file:${property_path}/common.properties", ignoreResourceNotFound = false),
#PropertySource(value = "file:${property_path}/service1.properties", ignoreResourceNotFound = true) })
public class MainClass {
static String logDirectory = ApplicationContext.getGlobalProperty("logging.file");
public static void main(String[] args) {
SpringApplication springApplication = new SpringApplication(MainClass.class);
Properties properties = new Properties();
properties.put("logging.file", logDirectory);
springApplication.setDefaultProperties(properties);
springApplication.run(args);
}
}
Application Context:
#Configuration
#EnableAutoConfiguration
public class ApplicationContext implements EnvironmentAware {
private static Environment environment;
#Override
public void setEnvironment(Environment environment) {
ApplicationContext.environment = environment;
}
public static String getGlobalProperty(String propertyName) {
return environment.getProperty(propertyName);
}
}
Here you can see any way I've used environment to get property. Is there any way to set the property using the environment such that while spring boot initialization itself the properties are populated?
We can also implement ApplicationContextInitializer and override initialize method to read properties. But how can I make it read 2 property files and override the duplicate property with the latest value? Reference(I'm not sure how to implement my requirements in this way.). Even in this case doesn't sound like you are trying to kill a mosquito with a hammer?
Current working Solution:
#Aspect
#Profile("!production")
#Configuration
public class ControllerAspect {
#pointcut(....)
} //Here also I've to pass spring.profiles.active as JVM params.
//setting the value in common.properties or service1.properties is not working.
I'm a newbie to spring boot so please let me know for additional clarifications.
It seems Spring by default loads some properties at initialization and unless until you specifically write logic to overwrite them (like the one I wrote in MainClass.java) there is no option to override those. Some of these include (logging.file, key used in #ConditionalonExpression).
Some tricks with their own challenges:
Specify the properties in application.properties in your classpath. The variables loaded at the earlier stages are always read from this file. challenge: I've tight coupled all my properties into the jar and in order to change the values I've to recompile and relaunch the Jar.
Use profiles and define application.properties as application-profile.properties. challenge: I've to create so many profiles and still the previous challenge exists.
Pass the property value as JVM parameter as -Dproperty.key=value. challenge:seriously? How many properties am I supposed to send as JVM parameter?
Implement ApplicationContextInitialize and override initialize method.challenge:Overriding Spring's default behaviour is not recommended as well as isn't it an overkill to use this just for reading property file?
Solution:
Use -Dspring.config.location to specify the property files. In this case, always spring reads the properties only from the specified location(s). You can provide multiple property files as well. Refer this for much more details. It seems if you give property locations as Directories spring loads them in reverse order. But if you specify files it follows the order specified.
Note: All these can be combined together. To know about precedence refer this.
I was wondering if there is any way to do this.
I want to hardcode a property (I know might not be the best), let's say I want to run my application always on port XXX or any other configuration without using a .properties.
Is there any way I can do this from the main? or a configuration bean?
Thanks.
Take a look at this for how to configure a port: Spring Boot - how to configure port
Relevant code is this:
#Controller
public class ServletConfig {
#Bean
public EmbeddedServletContainerCustomizer containerCustomizer() {
return (container -> {
container.setPort(8012);
});
}
In general, most properties that can be configured via application.properties can also be configured through a Java bean. But, I would suggest using application.properties if you can. It allows you to change properties, without having to change source code.
EDIT:
Some other code from the posted link you might find useful:
HashMap<String, Object> props = new HashMap<>();
props.put("server.port", 9999);
new SpringApplicationBuilder()
.sources(SampleController.class)
.properties(props)
.run(args);
I need to specify multiple template loader paths for FreeMarker in a Spring Boot web application but the FreeMarkerAutoConfigurationClass only let me specify one path using the spring.freemarker.templateLoaderPath property, which uses the setTemplateLoaderPath method in the FreeMarkerConfigurationFactory. However, this class allows me to set multiple path using the setTemplateLoaderPaths method. Which is the best way to override this auto-configuration class and specify multiple loader paths? I don't really understand well the Spring Java config classes and I want an example for this before write the code I need. I'm using Spring Boot 1.1.2. Thanks in advance.
You'll need to provide your own bean of type org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer with your desired configuration. To do so, add something similar to the following to one of your application's Java configuration classes:
#Bean
public FreeMarkerConfigurer freeMarkerConfigurer() {
FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
configurer.setTemplateLoaderPaths("one", "two", "three");
// Apply further configuration as needed
return configurer;
}
Update: the latest Spring Boot 1.2 snapshots now accept a comma-separated list for the spring.freemarker.templateLoaderPath property allowing you to specify multiple paths without declaring a custom FreeMarkerConfigurer bean.
Right now I'm exposing the service layer of my application using spring remoting's RMI/SOAP/JMS/Hessian/Burlap/HttpInvoker exporters. What I'd like is to allow the user to somehow define which of these remoting mechanisms they'd like enabled (rather than enabling all of them), then only create those exporter beans.
I was hoping that spring's application context xml's had support for putting in conditional blocks around portions of the xml. However, from what I've seen so far there's nothing in the standard spring distribution that allows you to do something like this.
Are there any other ways to achieve what I'm trying to do?
I am going to assume that you are looking to configure your application based on your environment, as in... for production I want to use this beans, in dev these other ...
As Ralph is saying, since Spring 3.1 you have profiles... But the key, is that you understand that you should put your environment based beans in different configuration files... so you could have something like dev-beans.xml, prod-beans.xml... Then in your main spring file, then you just invoke the appropriate one based on the environment that you are using... So profiles are only technique to do so... But you can also use other techniques, like have a system environmental variable, or pass a parameter in your build to decide which beans you want to use
You could realize this by using a Spring #Configuration bean, so you can construct your beans in java code. (see http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-java)
#Configuration
public class AppConfig {
#Bean
public MyService myService() {
if ( userSettingIshessian ) {
return new HessianExporter();
}else {
return new BurlapExporter();
}
}
}
Of course you need to get the user setting from somewhere, a system parameter would be easy, or config file, or something else.
Spring 3.1 has the concept of Profiles. My you can use them.
Okay, I'm confused here.
I have a properties file with some SQL scripts that I want to store at startup (using Spring MVC servlet container in Tomcat) for later use. Well I think I have the syntax for that down in the *-servlet.xml:
<util:properties
id="findQueries"
location="classpath:resources/FindQueries.properties" />
but I'm not sure how to access it programmatically. I really only need one query from this file in one function of a service-layer class at this point.
I'm fairly new to Spring in general, so I'm definitely not set in my ways. Any suggestions of how to do this better / different will be considered.
Thanks all!
Obviously there is more than a dozen ways of doing this, Easiest way of doing this using #Value autowiring..
#Value("#{findQueries.queryKey}")
String query;
One other way of doing is to autowire the properties and then lookup for the key...
You can hook up your properties object for where you need it, by injecting it (for example into your controller class):
#Autowired()
#Qualifier("findQueries")
private Properties findQueries;
private void setFindQueries(Properties findQueries) {
this.findQueries = findQueries;
}
public void someotherMethod() {
findQueries.getProperty(...)
}