Set default properties in a library with spring-boot - java

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

Related

How to decouple System.getenv from static field in java

I've a legacy code as below
#Service
public class CLConf {
static final String CI_ENV = System.getenv("ENV").toUpperCase(); <-- NPE when doing junits
// other config variables
}
This works when we run the code normally in application. but I've issues creating test cases that use class CLConf something like below.
#ExtendWith(SpringExtension.class)
#SpringBootTest
#ContextConfiguration(classes = CLConf.class) <<----------- ISSUE while loading bcz env is not set yet
public class CLServiceTest {
//test cases
}
currently it throws NPE because of .toUpperCase()
Since CI_ENV is directly being set from System.getenv, while writing class for Junits, this class is loaded using #ContextConfiguration. And I'm not sure where to set env variables for Test cases so that it doesn't break while loading CLConf.
How do I configure/do setup for test cases OR how do i decouple System.getenv so that I can supply my own config while testing?
Note: there are many System.getenv in class CLConf, above is just minified class.
You can provide test configurations using the locations or value attribute of the TestPropertySource annotation, directly defining your test properties in a file or selectly override properties like stated in the documentation:
#TestPropertySource is a class-level annotation that is used to
configure the locations() of properties files and inlined properties()
to be added to the Environment's set of PropertySources for an
ApplicationContext for integration tests.
Test property sources have higher precedence than those loaded from
the operating system's environment or Java system properties as well
as property sources added by the application declaratively via
#PropertySource or programmatically (e.g., via an
ApplicationContextInitializer or some other means). Thus, test
property sources can be used to selectively override properties
defined in system and application property sources

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.

Loading additional spring profiles from java config

Is there any possibility to load aditional spring profiles from java config?
I know that I can use -Dspring.profile.active argument and also add profiles to spring.profiles.include in application.properties.
What I need is to be able to activate profiles from java config. I've created PropertyPlaceholderConfigurer, where I'm adding some custom property files, which also contains property spring.profiles.include, all properties are load and it works ok, but spring doesn't activate any profiles which are inclded using this property.
#Bean
public static PropertyPlaceholderConfigurer ppc() throws IOException {
PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
ppc.setLocations(new ClassPathResource("properties/" + property + ".properties"));
ppc.setIgnoreUnresolvablePlaceholders(true);
return ppc;
}
The active spring profiles are defined in properties via the following configuration: spring.profiles.active:.
You should list in all the files that you import the profiles that they activate via the above configuration key.
EDIT
First, as per the official documentation the configuration spring.profiles.include is more suitable for unconditionally adding active profiles.
Second, I can assume that PropertyPlaceholderConfigurer is not suitable for what you want to achieve. The official documentation lists the ways you can Externalize Configuration. You can try to use #PropertySource:
#PropertySources({
#PropertySource(value = "classpath:application.properties"),
#PropertySource(value = "classpath:other.properties", ignoreResourceNotFound = true)
})
public class Application {
...
}
}
Additionally, you can try to list the other properties files in property spring.config.location inside application.properties as described here.

How to set Spring Boot's logging levels programmatically?

I know how to set the log level via environment variables and application properties.
Is there a way to set them programmatically?
I would like to set log levels for particular test classes (which are using SpringJUnit4ClassRunner and #SpringApplicationConfiguration), but not all of them, and without having a separate properties file for every combination.
I tried defining a non-lazy bean to add a new PropertySource to the environment; the method was called but it had no effect.
#Bean
#Lazy(false)
public PropertySource testProperties(ConfigurableEnvironment environment) {
PropertySource properties = new MapPropertySource("testProperties", Collections.singletonMap(
"logging.level.org.springframework.security", "DEBUG"
));
environment.getPropertySources().addFirst(properties);
return properties;
}
You can use #TestPropertySource on your test classes. Unlike the bean-based approach that you tried, #TestPropertySource will add the property source to the environment before the context starts which allows the properties to be picked up when the logging system is initialized.
Something like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(YourApplication.class)
#TestPropertySource(properties = "logging.level.org.springframework.security:DEBUG")
public class YourApplicationTests {
// …
}
Credit to Boris the Spider for the hints.
First, add an element <jmxConfigurator /> to the logback.xml.
Then, this code will work:
#Before
public void configureLogging() throws JMException {
ObjectName name = new ObjectName(String.format("ch.qos.logback.classic:Name=default,Type=%s", JMXConfigurator.class.getName()));
JMXConfiguratorMBean logbackMBean = JMX.newMBeanProxy(ManagementFactory.getPlatformMBeanServer(), name, JMXConfiguratorMBean.class);
logbackMBean.setLoggerLevel("org.springframework.security", "DEBUG");
}
Sadly, there doesn't appear to be a better way to build the object name: the Package hierarchy isn't traversable and the string "default" isn't accessible anywhere I can find.

How do I specify multiple templateLoaderPaths for Freemarker in Spring Boot?

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.

Categories