How to override a variable in Spring Boot properties file - spring-boot

I have an existing properties file like below
csv.requireresync=true
There are two actions in the system that will get and set value to it.
Every 5 minutes there will a process that will set the csv.requireresync to false.
Every 2 weeks there will be a process that will set the csv.requireresync to true.
I tried to do the above using #Component but not working
#Component
#ConfigurationProperties(prefix = "csv")
public class ResyncConfig {
private boolean requireresync;
public void setRequireresync(boolean requireresync) {
this.requireresync = requireresync;
}
public boolean isRequireresync() {
return requireresync;
}
}
The idea is when the I call setRequireresync to true the value must reflect in the application.properties file.
Is it possible achieve what I want ? or do I need a extra configuration file for that ?

Just set required values directly to ResyncConfig isntance. It is shared across application (if not using Scopes its singleton) and any changes made to it will be in effect. File is only for bootstraping config class with initial settings.

Related

Change value at Runtime in Spring Boot

I have the below code .Can I change the ThreadPoolExecutor thread number size at run time ?
I am using spring boot.
#Configuration
public class ExecutorConfig
{
#Value(numberOfThreads)
private String numberOfThreads ; // numberOfThreads is configured app.properties file
#Bean
public ThreadPoolExecutor executorConfig()
{
ThreadPoolExecutor e = Executors.newFixedThreadPool(numberOfThreads);
return e;
}
}
One option is to add a set method for the property numberOfThread and then provide a way to update it, like a new endpoint. But if your app restarts it will still get the previous value from application.properties.
Other option is to use Spring Cloud Config, but this may or may not be overkill for your case.
Also, this answer goes a bit deeper with some code examples to force a reload.

Load custom properties file in Spring Boot MVC Main

I have created a myApp.properties in resources folder location and mentioned the server.port in this file.
myApp.properties
myApp.server.port=8020
Now I want to read load this property into my application. But I have to read this before I actually a server.
Here I am trying to do like this
#SpringBootApplication
#ComponentScan(basePackages = {"com.myorg.myapp" })
#EnableConfigurationProperties
#PropertySource("classpath:myApp.properties")
#Component
public class MyAppApplication {
#Value("${myApp.server.port}")
private static String serverPort;
public static void main(String[] args) throws Exception{
try {
SpringApplication appCtxt = new SpringApplication(MyAppApplication.class);
appCtxt.setDefaultProperties(Collections
.singletonMap("server.port", serverPort));
appCtxt.run(args);
} catch (Exception e) {
e.printStackTrace();
}
}
But serverPort is coming as null.
I also tried to create a separate Config file like this but it can't be accessed in static main
#Configuration
#PropertySource("myApp.properties")
#ConfigurationProperties
public class MyAppConfig {
#Value("${myApp.server.port}")
private String serverPort;
/**
* #return the serverPort
*/
public String getServerPort() {
return serverPort;
}
}
Any suggestion would be helpful.
Spring boot injects properties during the initialization of the application context.
This happens (gets triggered) in the line:
appCtxt.run(args);
But you try to access the property before this line - that why it doesn't work.
So bottom line, using "#Value" in the main method doesn't work and it shouldn't.
Now from the code snippet, it looks like you could merely follow the "standards" of spring boot and create the file application.properties with:
server.port=1234
The process of starting the embedded web server in spring boot honors this property and bottom line it will have the same effect and Tomcat will be started on port 1234
Update 1
Based on OP's comment:
So, how can I have multiple application.properties.
In the Spring Boot's documentation it is written that application.properties are resolved from the classpath. So you can try the following assuming you have different modules A,B,C and web app D:
Create src/main/resources/application.properties inside each of 4 modules and pack everything together. The configuration values will be merged (hopefully they won't clash)
If you insist on naming properties A.properties, B.properties and C.properties for each of non-web modules, you can do the following (I'll show for module A, but B and C can do the same).
#Configuration
#PropertySource("classpath:A.properties")
public class AConfiguration {
}
Create in Module A: src/main/resources/A.properties
If you need to load the AConfiguration automatically - make the module A starter (using autoconfig feature of spring-boot):
Create src/resources/META-INF/spring.factories file with the following content:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
<package_of_AConfiguration>.AConfiguration
Also this has been the requirement to separate C from entire bundle where it might run as bundle for some and as a separate for some others
Although I haven't totally understood the requirement, but you can use #ConditionalOnProperty for configuration CConfiguration (that will be created just like AConfiguration.java in my previous example) but this times for module C.
If the conditional is met, configuration will run and load some beans / load its own properties or whatever. All in all conditionals (and in particular Profiles in spring) can help to reach the desired flexibility.
By default, the application.properties file can be used to store property pairs, though you can also define any number of additional property files.
If you save myApp.server.port=8020 in application.properties, it will work fine.
To register a custome property file, you can annotate a #Configuration class with the additional #PropertySource annotation:
#Configuration
#PropertySource("classpath:custom.properties")
#PropertySource("classpath:another.properties")
public class ConfigClass {
// Configuration
}
make sure, your class path is correct.

Active profile in SpringBootTest based on system variable

As the host of Redis is different in local and CI, my #Tests can pass locally, they can't pass in CI.
Firstly, I tried to mock the RedisTemplate like this:
RedisTemplate redisTemplate = mock(RedisTemplate.class);
ValueOperations valueOperations = mock(ValueOperations.class);
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
when(valueOperations.increment(anyString(), anyLong())).thenReturn(1L);
when(valueOperations.get("a#a.com")).thenReturn("1");
It did mocked RedisTemplate, but can't mock redisTemplate.opsForValue() and valueOperations.increment(...) ( I can't find reason )
Then, I wrote two profiles named application-ci-test.yml and applicaiton-test.yml, tried to active one of them based on system environment variable
I learnd from here that I can set active profile in this way:
#Configuration
public class MyWebApplicationInitializer
implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
servletContext.setInitParameter(
"spring.profiles.active", "dev");
}
}
and this way:
#Autowired
private ConfigurableEnvironment env;
...
env.setActiveProfiles("someProfile");
But I don't know how to use them. The system variable can get by System.getenv(..). So now I want to know how to active profile based on the variable I get.
I found a way to active corresponding profile based on system variable or property:
import org.springframework.test.context.ActiveProfilesResolver;
public class SpringActiveProfileResolver implements ActiveProfilesResolver {
#Override
public String[] resolve(Class<?> testClass) {
final String isCITest = System.getEnv("isCITest");
return new String[] { isCITest == null ? "test" : "ci-test" };
}
}
then use the parameter resolver in #ActiveProiles:
#ActiveProfiles(resolver = SpringActiveProfileResolver.class)
How to set environment variable is anther issue, and answers above have already answered it
Assuming your #Test methods are in a class with the #SpringBootTest annotation, you can use #ActiveProfiles to set the profile.
#SpringBootTest
#ActiveProfiles("someProfile")
Use run parameters inside your CI job/script.
Depending how You start Your tests, You can for example do it with VM arguments
mvn test -Dspring.profiles.active=ci-test
or
java -jar -Dspring.profiles.active=ci-test
or whatever.
On the other hand you can use program arguments:
java -jar --spring.profiles.active=ci-test
One way or the other, providing active profile at start will activate property file of your choice.
If you want some specific piece of code (configuration class for example) to be run with specific profile only, annotate that piece of code with #Profile("ci-test")
Example:
#Configuration
#Profile("ci-test")
public class SomeConfiguration {
//any configuration beans etc.
}
Following class will only be loaded when Your active profile will be "ci-test". So if You run Your app on Your CI server with one of the commands above, both property file named "ci-test" and this configuration class will get loaded.
It's also worth adding that in order for some code to run in ALL profiles EXCEPT specified, you can negate the name inside profile annotation like: #Profile("!ci-test").
Code annotated like that will run with all profiles (including default) except "ci-test".

How to prevent a property beeing set on certain profile

we want to implement a feature for enabling a user to choose a role in the system, by sending the role he/she wishes to have in the login request.
this feature is meant for testing (creating test-users or assigning roles to existing ones is "impossible" in the customers system) and, of course, should never be deployed to a production environment.
I want to deployment of my application to fail if the property feature.choose-role is set to true AND the spring active profile is set to production.
As we are using springs config-server features, i also want to application to completely stop working if the property is set to true at runtime.
My first attempt was to simply create this Config:
#Configuration
public class FeatureToggleGuardConfig {
#Bean
#RefreshScope
#ConditionalOnProperty(value = "feature.choose-roles", havingValue = "true")
#Profile("production")
public Object preventDeploymentOfRoleChoosingFeatureOnProduction() {
throw new RuntimeException("feature.choose-roles must not be true in production profile!");
}
}
This works if the property is set to true at deployment, but as i understand, will only attempt to refresh the bean if someone actually tries to use it - which will never happen.
Also - i don't think that it would stop the whole application if this just threw a runtime exception when it is used.
in short:
I want to prevent my application to run (or keep running) if at any time, the property feature.choose-roles is true and the active profile is "production".
I do not want to alter production code in order to do this ( if(feature is enables && profile is production) etc.)
Perhaps instead of having a your profile drive some sort of blocker, you can have your profile drive a config bean which says whether or not to use the feature. Then, have the nonProd config read from your property, and have the prod config always return false.
Something like:
public interface ChooseRolesConfig {
boolean allowChoosingRoles();
}
#Profile("!production")
public class NonProdChooseRolesConfig implements ChooseRolesConfig {
#Value("${feature.choose-roles}")
boolean chooseRoles;
#Override
public boolean allowChoosingRoles() {
return chooseRoles;
}
}
#Profile("production")
public class ProdChooseRolesConfig implements ChooseRolesConfig {
#Override
public boolean allowChoosingRoles() {
return false;
}
}
and then just autowire a ChooseRolesConfig object, and call the method, and regardless of what you change feature.choose-roles to using cloud config, it should always be false for prod.
Disclaimer: I blindly wrote this so it might be missing some annotations or something but hopefully you get the idea

Can you or can't you use a property from a config file when using Spring #Condition?

I have read pretty much everything I can find on StackOverflow and other sites and I don't see a definitive answer anywhere.
I have a class that implements #Condition that I use in a #Configuration file to conditionally load some beans. I am doing something like this:
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metdata) {
String property = context.getEnvironment().getProperty("some.prop.from.file");
boolean enable = Boolean.parseBoolean(property);
return enable;
}
}
When debugging I see that getting the property from the environment always returns null, even though the property is injected in other beans using #Value.
So my question can you or can't you attempt to get a property value from a file within a #Condition class? Can you only get System properties? I would think that this is a common use case that I would think Spring could handle.
Had to add the property to application.properties and not the other property files that are loaded during startup.

Resources