How to autowire Enviroment into a PropertySourcesPlaceholderConfigurer? - java

I'm trying to set up my Spring app such that a different .properties files is read depending on the configuration profile. I'm using java config and so what I'm trying to do is this:
#Autowired
private static Environment env;
#Bean
public static PropertySourcesPlaceholderConfigurer properties(){
PropertySourcesPlaceholderConfigurer pspc = new PropertySourcesPlaceholderConfigurer();
String[] profiles = env.getActiveProfiles();
String filestring = "environment."+profiles[0]+".properties";
ClassPathResource properties = new ClassPathResource( filestring );
Resource[] resources = new ClassPathResource[] { properties };
pspc.setLocations( resources );
return pspc;
}
However the env.getActiveProfiles() is giving me a NullPointerException, which I assume means that the environment hasn't been injected. Any one got any ideas how I can fix this? Or alternatively if this is dumb/impossible how I could go about this better?

Just to give you an alternate perspective on your approach (clearly everyone's business case may differ from project to project,) but the type of configuration you are pursuing might lead to other headaches down the road. Security comes to mind. Usually multiple environments means you are dealing with usernames and passwords for various connections to databases and such. Storing those values for a production environment in line with your other configurations could expose sensitive data to developers who need have no knowledge of such things. Rather, if you switch using SPeL expressions and referencing the environment directly, then you can still achieve your runtime configuration but move your settings for each environment to the server (or what-have-you) where those specific configs apply. Example:
<bean id="myDatabase" class="mypackage.MyDatabase" p:username="#{environment['DB_USERNAME']}" p:password="#{environment['DB_PASSWORD']}" .../>
Then on your server, you can pass in system properties OR set environment variables with your desired username and password, and they will be configured at runtime. (The environment expression resolves directly to your Environment instance.)
Just a thought. =)

As #kungfuters rightly suggested, the business case may differ from application to application. Here is another alternative that worked for my application.
Provide an implementation of following interface:
ApplicationContextInitializer<ConfigurableApplicationContext>
Provide implementation of the following method. The logic to identify the profile goes in this method.
initialize(ConfigurableApplicationContext ctx)
Based on the identification, set the active profile:
this.applicationContext.getEnvironment().setActiveProfiles(<<yourProfileName>>)

Related

Spring boot properties to be loaded at initialization and respect all and control #Aspect based on the value from property file

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.

Update values on properties file on Runtime

I've configuration as below:
#Configuration
public class PropertyConfiguration {
#Bean
#Profile("local")
public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
PropertyPlaceholderConfigurer configurer = new PropertyPlaceholderConfigurer();
configurer.setLocation(new FileSystemResource("path/to/resources/app-local.properties"));
configurer.setIgnoreUnresolvablePlaceholders(true);
return configurer;
}
}
My app-local.properties file contains values as:
cache.time.milliseconds=1000
So, I'm accessing the value as:
#Value("${cache.time.milliseconds}")
private long cachingTime;
I'm getting the correct value.
System.out.println(cachingTime);
Now, I want to update the cachingTime to some other value and serve that updated value. For example, from 1000 to 99.
Is there any way to update this property value at runtime??
Or is there any other way to update this value except restarting app or server?
I'm using Spring Boot 1.4.3.RELEASE.
I tried to google it, but none of answer gave me the solution. :(
Thank you for any help.
If you will change the value on property file it will not effect on runtime because all the configuration is done while server starting, if you don't want to redeploy the code base you can do one thing, change the property file value and just restart the server.
You can have a look at spring-boot admin once. Though it acts as a monitoring server, it gives you ability to update properties and environment variables.
http://codecentric.github.io/spring-boot-admin/1.5.3/
Attaching a screenshot put up a proof of concept by codecentric guys.

Set default properties in a library with spring-boot

I have many different services using spring-boot. I'd like to set up some configuration that is common for each, but allow the services to have their own properties and override them if they want. Example properties include spring.show_banner, management url ones, etc.
How can I do this? If I have the following:
service-common with src/main/resources/application.yml with default properties
service1 with src/main/resources/application.yml with its own properties
I'd like them to be merged with the service1 version taking precedence. Instead, it seems that only the first one found on the classpath is used.
(Alternatively, using #Configuration classes would be even better, but I'm not sure they can be used to define many of the properties)
There are several options available to you, all based on the order in which property sources are considered.
If your common library is responsible for creating the SpringApplication it can use setDefaultProperties. These values can be overridden by your services' application.properties.
Alternatively, your library could use #PropertySource on one of its #Configuration classes to configure, for example, library.properties as a source. Again, these properties could then be overriden in your services' application.properties.
I am not sure what you mean by merging them.
But I'm assuming that in the end, you are describing the situation where you have profile-specific configuration. Because, any properties that are specific to a certain service can be managed/injected using Spring profiles, which will always take precedence over default property files (see documentation).
For example, you can have the file application-service1.properties which would automatically be used when you run your app with the property spring.profiles.active=service1, which can be specified in the command line and other places.
If you don't specify this property, Spring Boot will fallback to the default application.properties file.
And you can of course write the common properties in both files:
application.properties:
service.url=http://localhost:8080/endpoint
service.user=admin
service.password=admin
application-service1.properties:
service.url=http://api.service.com/endpoint
service.user=admin
service.password=aosdnoni3
public class MyApplicationListener implements ApplicationListener<ApplicationEvent> {
#Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
ApplicationEnvironmentPreparedEvent envEvent = (ApplicationEnvironmentPreparedEvent) event;
ConfigurableEnvironment env = envEvent.getEnvironment();
Properties props = new Properties();
//set props as desired
env.getPropertySources()
.addFirst(new PropertiesPropertySource("customname", props));
}
}
Then in src/main/resources/META-INF/spring.factories, add line:
org.springframework.context.ApplicationListener=mypackage.MyApplicationListener

Spring Boot programmatic logging configuration

How can I configure logging programmatically in a spring boot application?
Using an xml or properties file is not flexible enough for my needs.
Update: I want to achieve something like this:
#Value("${logging.level.root}")
private String loggingLevelRoot;
#Value("${logging.level.myApp}")
private String loggingLevelMyApp;
#Value("${logging.file}")
private boolean fileAppenderEnabled;
....
setLevel(Logger.ROOT_LOGGER_NAME, Level.toLevel(loggingLevelRoot)));
setLevel("com.myapp", Level.toLevel(loggingLevelMyApp)));
setLevel("org.springframework", Level.WARN);
setLevel("org.apache.coyote", Level.INFO);
setLevel("org.apache.catalina", Level.INFO);
setLevel("org.apache.catalina.startup.DigesterFactory", Level.ERROR);
setLevel("org.apache.catalina.util.LifecycleMBeanBase", Level.ERROR);
Logger logger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
logger.addAppender(createConsoleAppender());
if (fileAppenderEnabled) {
logger.addAppender(createFileAppender());
}
All I have per environment is:
logging.level.root=[INFO, DEBUG, ..]
logging.level.myApp=[INFO, DEBUG, ..]
logging.file=[true | false]
No duplication of XML, Groovy and other formats I really don't want to deal with.
At the end of the day, this is really about achieving the same flexibility for logging as Spring JavaConfig did for beans. XML or other file formats are simply too static, require too much duplication and not integrated well enough with the rest of the configuration of the application.
Why should logging be configured differently than any other bean or service? It makes no sense.
I'm not sure you want or need to disable the default XML configuration of the logging system, but you do want to execute your customization calls after that was done. Fortunately that's pretty easy as it's done as early as possible in the initializer chain for a SpringApplication. The easiest place to put your code is probably a SpringApplicationInitializer (it has to implement ApplicationContextInitializer as well so it can be added to the SpringApplication). E.g.
SpringApplication application = new SpringApplication(MySources.class);
application.addInitializers(new LoggingInitializer());
application.run(args);
You won't be able to do dependency injection into the initializer if you do it that way, but it will ensure that it gets called as early as possible in the lifecycle. If your initializer implements EnvironmentAware then you will also be passed an instance of Environment before the call to SpringApplicationInitializer.initialize() - using that you can resolve the environment dependent pieces in your sample, e.g.
String loggingLevelRoot = environment.getProperty("logging.level.root");
Once you have it working, to avoid having to do the same thing for all apps you can make it declarative by adding a META-INF/spring.factories containing your initializer class:
org.springframework.context.ApplicationContextInitializer=\
my.pkg.for.LoggingInitializer
If you really need dependency injection and #Value resolution I think you are going to have to accept that the ApplicationContext will have fully refreshed before you get a chance to configure anything. If that's an acceptable compromise I recommend just adding a LoggingInitializer to your context and have it implement CommandLineRunner.

Conditionally creating beans in spring

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.

Categories