Spring changing the properties file in runtime - java

I'm using Spring Boot and I have a properties file p.properties:
p1 = some val1
p2 = some val2
Configuration class:
#Configuration
#PropertySource("classpath:p.properties")
public class myProperties {
public myProperties () {
super();
}
#Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
And I'm using this in order to access the property:
#Value("${p1}")
private String mProperty;
Everything works great.
I want to change p1 in p.properties file from outside of the app and the next time that I'll use mProperty, it will contains the new value without restarting the app.
Is it possible?
Thanks,
Avi

You can simply use spring boot actuator.
Just add the actuator dependency in your maven/gradle config and you should be seeing live reloads when you update the property file.
Note: You won't have to restart the app but actuator will do live reloads on its own.

If you want to change the properties at runtime and don't want to restart the server then follow the below steps:
Application.properties
app.name= xyz
management.endpoints.web.exposure.include=*
Add below dependencies in pom.xml
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-context
2.0.1.RELEASE
3)Place application.properties in config folder . The config folder must be in the location from where you will run the jar.
Add ApplcationProperties.java
#RefreshScope
#Component
#ConfigurationProperties(prefix = "app")
public class ApplcationProperties {
private String name;
//getter and setter
}
Write ApplicationController.java and inject ApplcationProperties
#Autowired
ApplcationProperties applcationProperties;
#RestController
public Class ApplicationController
{
#GetMapping("/find")
String getValue()
{
return applicationProperties.getName();
}
}
Run the spring boot application
Call localhost:XXXX/find from your browser
Output : xyz
Change the value in application.properties from xyz to abc
Using postman send a put /options request to localhost:XXXX/actuator/refresh
--Note this request should be either PUT/OPTIONS
Call localhost:XXXX/find from your browser
Output : abc

I think, in this case, it is advisable to keep it in the database so that, it can be changed & accessed seamlessly. We have a similar scenario where we keep the encrypted password for database in the properties file. While connecting to db, it needs to be decrypted. We do that by extending PropertyPlaceholderConfigurer as follows.
public class MyPropertyConfigurer extends PropertyPlaceholderConfigurer{
protected void convertProperties(Properties props){
Enumeration<?> propertyNames = props.propertyNames();
while (propertyNames.hasMoreElements()) {
String propertyName = (String) propertyNames.nextElement();
String propertyValue = props.getProperty(propertyName);
if(propertyName.indexOf("db.password") != -1){
decryptAndSetPropValue(props,propertyName,propertyValue);
}
}
}
}
But, this is done only once while loading the properties file.

Related

Cache Loading in Spring Boot

I have a cache class that is loading properties from a .properties file before the application startup, in a simple java project. It logs out everything.I need to convert this project to springboot application.
What annotations can i use to achieve the Cache Loading??
Currently I wrote the code like my spring boot app starts with the #postconstruct , since i am not using web.xml for loading servlets.
#RestController
public class ConfigServlet {
#PostConstruct
public void init() {
//business logic
}
}
And this servlet starts up first. So how can i load cache along this??
It is supposed to load the Cache even before this servlet class loads. How can i achieve this concept??
Suppose you have below properties in your application properties. You can load them as below. The properties will be loaded during the application startup.
application.properties
test.a = 10
test.b =20
test1.a = 30
test1.b = 40
#Configuration
#ConfigurationProperties
Class CacheProperties {
Map<String,String> test;
Map<String,String> test1;
public String getTestB() {
return test.get("b");
}
public String getTestA() {
return test.get("a");
}
public String getTest1B() {
return test1.get("b");
}
public String getTest1A() {
return test1.get("a");
}
//setters
}

Should I mock Spring Cloud Config server properties during tests?

How do I test a service that has properties from spring cloud config server injected into it as a dependency?
-Do I simply create my own properties during testing using the new keyword?(new ExampleProperties())
Or do I have to use spring and create some kind of test properties and use profiles to tell which properties to use?
Or should I just let spring call the spring cloud config server during testing?
My service looks like the one below:
#Service
class Testing {
private final ExampleProperties exampleProperties
Testing(ExampleProperties exampleProperties) {
this.exampleProperties = exampleProperties
}
String methodIWantToTest() {
return exampleProperties.test.greeting + ' bla!'
}
}
My project makes a call to a spring cloud config server during start up to get properties, this is enabled by having the following on the bootstrap.properties:
spring.cloud.config.uri=http://12.345.67.89:8888
I have a configuration that looks like the one below:
#Component
#ConfigurationProperties
class ExampleProperties {
private String foo
private int bar
private final Test test = new Test()
//getters and setters
static class Test {
private String greeting
//getters and setters
}
}
The properties file looks like this:
foo=hello
bar=15
test.greeting=Hello world!
You can use #TestPropertySource annotation to fake properties during test:
#ContextConfiguration
#TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
public class MyIntegrationTests {
// class body...
}
For Unit test just simply mock Properties and use Mockito methods when(mockedProperties.getProperty(eq("propertyName")).thenReturn("mockPropertyValue") and it will be fine.
For Integration test all Spring context should be inited and work as regular app, in that case you dont need to mock your properties.
Another option is to use properties attribute of SpringBootTest annotation:
#SpringBootTest(properties = {"timezone=GMT", "port=4242"})

Get Map of properties from spring-boot Environment

I have such a problem: mine application works with set of other applications. Each of them has some unique property - templateProject-id. So, application.properties looks like
mine.application.basic-project-id.application_1=10200
mine.application.basic-project-id.application_2=10202
mine.application.basic-project-id.application_3=10001
I don't want to store Environment object in my service class. Only Map<String, Long> for pairs (application_name, project_id)
So, from that example should contain pairs ("application_1", 10200L), ("application_2",10202L), ("application_3",10001").
Right now I store Environment and with application name I build property name each time and retrieve value.
String projectIdPropertyName = String.format("mine.application.basic-project-id.%s", applicationDescriptor.getName());
String softwareBasicProjectId = Long.valueOf(environment.getProperty(projectIdPropertyName));
This should work
#ConfigurationProperties("mine.application")
public class ApplicationProperties {
private Map<String,Long> basicProjectId = new HashMap<>();
public Map<String,Long> getBasicProjectId() {
return basicProjectId;
}
}
#SpringBootApplication
#EnableConfigurationProperties(ApplicationProperties.class)
public class YourApp { .... }
Then anywhere you need that stuff, just inject ApplicationProperties. If you enable the Spring Boot configuration meta-data annotation processor to your build you'll also get content assistance for that key (and any other key you'd add in that class).

Can I manually load #ConfigurationProperties without the Spring AppContext?

Is there any way to load a class marked with #ConfigurationProperties without using a Spring Context directly? Basically I want to reuse all the smart logic that Spring does but for a bean I manually instantiate outside of the Spring lifecycle.
I have a bean that loads happily in Spring (Boot) and I can inject this into my other Service beans:
#ConfigurationProperties(prefix="my")
public class MySettings {
String property1;
File property2;
}
See the spring docco for more info http://docs.spring.io/spring-boot/docs/current-SNAPSHOT/reference/htmlsingle/#boot-features-external-config-command-line-args
But now I need to access this bean from a class that is created outside of Spring (by Hibernate). The class is created so early in the app startup process that Spring Boot has not yet made the application context available through the classic lookup helper methods or roll-my-own static references.
So I instead want to do something like:
MySettings mySettings = new MySettings();
SpringPropertyLoadingMagicClass loader = new SpringPropertyLoadingMagicClass();
loader.populatePropertyValues(mySettings);
And have MySettings end up with all its values loaded, from the command line, system properties, app.properties, etc. Is there some class in Spring that does something like this or is it all too interwoven with the application context?
Obviously I could just load the Properties file myself, but I really want to keep Spring Boot's logic around using command line variables (e.g. --my.property1=xxx), or system variables, or application.properties or even a yaml file, as well as its logic around relaxed binding and type conversion (e.g. property2 is a File) so that it all works exactly the same as when used in the Spring context.
Possible or pipe dream?
Thanks for your help!
I had the same "issue".
Here is how I solved it in SpringBoot version 1.3.xxx and 1.4.1.
Let's say we have the following yaml configuration file:
foo:
apis:
-
name: Happy Api
path: /happyApi.json?v=bar
-
name: Grumpy Api
path: /grumpyApi.json?v=grrr
and we have the following ConfigurationProperties:
#ConfigurationProperties(prefix = "foo")
public class ApisProperties {
private List<ApiPath> apis = Lists.newArrayList();
public ApisProperties() {
}
public List<ApiPath> getApis() {
return apis;
}
public static class ApiPath {
private String name;
private String path;
public String getName() {
return name;
}
public void setName(final String aName) {
name = aName;
}
public String getPath() {
return path;
}
public void setPath(final String aPath) {
path = aPath;
}
}
}
Then, to do the "magic" things of Spring Boot programmatically (e.g. loading some properties in a static method), you can do:
private static ApisProperties apiProperties() {
try {
ClassPathResource resource;
resource = new ClassPathResource("/config/application.yml");
YamlPropertiesFactoryBean factoryBean;
factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setSingleton(true); // optional depends on your use-case
factoryBean.setResources(resource);
Properties properties;
properties = factoryBean.getObject();
MutablePropertySources propertySources;
propertySources = new MutablePropertySources();
propertySources.addLast(new PropertiesPropertySource("apis", properties));
ApisProperties apisProperties;
apisProperties = new ApisProperties();
PropertiesConfigurationFactory<ApisProperties> configurationFactory;
configurationFactory = new PropertiesConfigurationFactory<>(apisProperties);
configurationFactory.setPropertySources(propertySources);
configurationFactory.setTargetName("foo"); // it's the same prefix as the one defined in the #ConfigurationProperties
configurationFactory.bindPropertiesToTarget();
return apisProperties; // apiProperties are fed with the values defined in the application.yaml
} catch (BindException e) {
throw new IllegalArgumentException(e);
}
}
Here's an update to ctranxuan's answer for Spring Boot 2.x. In our situation, we avoid spinning up a Spring context for unit tests, but do like to test our configuration classes (which is called AppConfig in this example, and its settings are prefixed by app):
public class AppConfigTest {
private static AppConfig config;
#BeforeClass
public static void init() {
YamlPropertiesFactoryBean factoryBean = new YamlPropertiesFactoryBean();
factoryBean.setResources(new ClassPathResource("application.yaml"));
Properties properties = factoryBean.getObject();
ConfigurationPropertySource propertySource = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(propertySource);
config = binder.bind("app", AppConfig.class).get(); // same prefix as #ConfigurationProperties
}
}
The "magic" class you are looking for is PropertiesConfigurationFactory. But I would question your need for it - if you only need to bind once, then Spring should be able to do it for you, and if you have lifecycle issues it would be better to address those (in case they break something else).
This post is going into similar direction but extends the last answer with also validation and property placeholder resolutions.
Spring Boot Binder API support for #Value Annotations
#Value annotations in ConfigurationPropertys don't seem to bind properly though (at least if the referenced values are not part of the ConfigurationProperty's prefix namespace).
import org.springframework.boot.context.properties.bind.Binder
val binder = Binder.get(environment)
binder.bind(prefix, MySettings.class).get

How do I configure Spring to partially and optionally override properties?

I would like to have a properties setup which can, on certain environments, override specific properties. For example, our default JDBC properties for dev are:
db.driverClassName=com.mysql.jdbc.Driver
db.url=jdbc:mysql://localhost:3306/ourdb
db.username=root
db.password=
The problem is that some of our devs would like to have a different username/password on the db, or possibly even a non locally hosted db. The same is true for our rabbitMQ configuration, which currently uses a similar localhost, guest/guest setup. Being able to override the properties of certain elements of this configuration on a per-developer basis would allow us to move much of the infrastructure/installation requirements for building the software off the local machine and onto dedicated servers.
I have set-up a simple project to wrap my head around the configuration required to achieve what I want, and this is my first foray into the world of spring property configuration, since up till now, property loading and management is done with some custom code. Here is my setup:
class Main_PropertyTest {
public static void main(String[] args) {
String environment = System.getenv("APPLICATION_ENVIRONMENT"); // Environment, for example: "dev"
String subEnvironment = System.getenv("APPLICATION_SUB_ENVIRONMENT"); // Developer name, for example: "joe.bloggs"
System.setProperty("spring.profiles.active", environment);
System.setProperty("spring.profiles.sub", subEnvironment);
try(AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(PropertyTestConfiguration.class)) {
Main_PropertyTest main = context.getBean(Main_PropertyTest.class);
main.printProperty();
}
}
private final String property;
public Main_PropertyTest(String property) {
this.property = property;
}
public void printProperty() {
System.out.println("And the property is: '" + property + "'.");
}
}
And my configuration:
#Configuration
public class PropertyTestConfiguration {
#Bean
public static PropertySourcesPlaceholderConfigurer primaryPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.active") + ".main.properties"));
return propertySourcesPlaceholderConfigurer;
}
#Bean
public static PropertySourcesPlaceholderConfigurer secondaryPlaceholderConfigurer() {
PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer = new PropertySourcesPlaceholderConfigurer();
propertySourcesPlaceholderConfigurer.setLocation(new ClassPathResource(System.getProperty("spring.profiles.sub") + ".main.properties"));
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertySourcesPlaceholderConfigurer.setIgnoreResourceNotFound(true);
propertySourcesPlaceholderConfigurer.setOrder(-1);
return propertySourcesPlaceholderConfigurer;
}
#Bean
public Main_PropertyTest main_PropertyTest(#Value("${main.property}") String property) {
Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
return main_PropertyTest;
}
}
And for completeness, my dev.main.properties and test.main.properties:
main.property=dev
main.property=test
The main problem is that I get an illegal argument exception. As far as I can tell, what I have written should be the javaconfig equivalent of this method: http://taidevcouk.wordpress.com/2013/07/04/overriding-a-packaged-spring-application-properties-file-via-an-external-file/
Unfortunately I get the following error: java.lang.IllegalArgumentException: Could not resolve placeholder 'main.property' in string value "${main.property}". Note that I also need to take care of the case where there is no sub-environment, and this is the case I have started with (although I get the same error even if both files exist). If I remove the bean which sets up the second propertysourcesplaceholderconfigurer, then it all works fine (by which I mean dev.main.properties is loaded and "And the property is: 'dev'." is printed out).
A secondary problem is that the code doesn't look great, and each layer of the system will need two PSPC's set-up so that they can access these properties. Furthermore, it requires a lot of manual calls to System.getProperty(), since I couldn't pass ${spring.profiles.active} to PSPC.setLocation();
Note: I have tried #PropertySources({primaryproperties, secondaryProperties}), but this fails because secondaryProperties does not exist. I have also tried #Autowired Environment environment; and getting the properties from that, but the secondary PSPC causes the environment to not be autowired...
So following this lengthy explanation, my questions are:
Is this the right way of solving this problem?
If so, what is wrong with my configuration?
How can I simplify the configuration (if at all)?
Is there an alternative mechanism available which would solve my problem?
Thank you for your time! :)
Your configuration is flawed when configuring BeanFactoryPostProcessor with java config the methods should be static. However it can be even easier, instead of registering your own PropertySourcesPlaceholderConfigurer utilize the default #PropertySource support.
Rewerite your jav config to the following
#Configuration
#PropertySource(name="main", value= "${spring.profiles.active}.main.properties")
public class PropertyTestConfiguration {
#Autowired
private Environment env;
#PostConstruct
public void initialize() {
String resource = env.getProperty("spring.profiles.sub") +".main.properties";
Resource props = new ClassPathResource(resource);
if (env instanceof ConfigurableEnvironment && props.exists()) {
MutablePropertySources sources = ((ConfigurableEnvironment) env).getPropertySources();
sources.addBefore("main", new ResourcePropertySource(props));
}
}
#Bean
public Main_PropertyTest main_PropertyTest(#Value("${main.property}") String property) {
Main_PropertyTest main_PropertyTest = new Main_PropertyTest(property);
return main_PropertyTest;
}
}
This should first load the dev.main.properties and additionally the test.main.properties which will override the earlier loaded properties (when filled ofcourse).
I had a similar issue with overwriting already existing properties in integration tests
I came up with this solution:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = {
SomeProdConfig.class,
MyWebTest.TestConfig.class
})
#WebIntegrationTest
public class MyWebTest {
#Configuration
public static class TestConfig {
#Inject
private Environment env;
#PostConstruct
public void overwriteProperties() throws Exception {
final Map<String,Object> systemProperties = ((ConfigurableEnvironment) env)
.getSystemProperties();
systemProperties.put("some.prop", "test.value");
}
}

Categories

Resources