'Spring boot configuration: how to return always same random value when referenced?
I have the application property APP_ID that should be randomly generated (UUID) and that should have the same value for the entire Spring Boot application.
What I did was the following: I defined in the application.properties file the APP_ID=${random.uuid}.
The UUID gets created successfully, however for every property reference @Value("${APP_ID}") I will get a different UUID.
Example: In class Foo I want to use the appId:
@Value("${APP_ID}")
private String appId;
In class Bar I want to use the appId, too:
@Value("${APP_ID}")
private String appId;
However, the appId in Bar is always different to the appId in Foo.
I have read in this thread that this behavior is the correct one.
What would be proper implementation to always get the same APP_ID?
Solution 1:[1]
One way to do it (as suggested by wilkinsoa in this thread) is to "bind a single random value into a @ConfigurationProperties annotated bean and then use that bean to configure anything else that needed the same value."
This results in an application.properties file:
app.id=${random.uuid}
The configuration properties file is:
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private String id;
public String getId() {
return this.id;
}
public void setId(String id) {
this.id = id;
}
}
The class using the id:
@Component
public class DoStuff {
private AppProperties appProperties;
@Autowired
public DoStuff(AppProperties appProperties) {
this.appProperties = appProperties;
}
}
Solution 2:[2]
You can generate random value as constant for your test.
package com.yourdomain.config;
import org.apache.commons.lang3.RandomUtils;
public class TestConfig {
public static final long RANDOM_LONG = RandomUtils.nextLong();
}
and then reference it like:
integration.test.random.seed=#{T(com.yourdomain.config.TestConfig).RANDOM_LONG}
rabbitmq.queue=queueName_${integration.test.random.seed}
Solution 3:[3]
You can put system property directly in properties file if it is not set. The following code:
- Checks for
APP_UUIDsystem prop. - If
APP_UUIDis not set, sets randomUUIDtoAPP_UUIDsystem prop, and sets the same value toAPP_IDspring prop. - If
APP_UUIDis set, uses this value forAPP_IDspring prop.
Code (for YAML format. For .properties format "" marks should be omitted):
APP_ID: "#{systemProperties['APP_UUID'] = (systemProperties['APP_UUID'] ?: '${random.uuid}') }"
Note: System prop can have same name as spring prop, so:
APP_ID: "#{systemProperties['APP_ID'] = (systemProperties['APP_ID'] ?: '${random.uuid}') }"
Solution 4:[4]
I think I've found the most simple but harder to document solution.
The issue with using a class to store the value and autowiring is that you still lose the stateful-ness of the environmental variable. If someone makes a mistake and tries to reference it again you could easily run into some errors.
@Configuration
public class FileSharingConfiguration {
private final static UUID app_id = UUID.randomUUID();
@PostConstruct
private void init() {
System.setProperty("app.instance-id", app_id.toString());
}
}
I wouldn't rely on #{random.uuid} because it makes references stateless, so I generate my own in the program. Then in a post construction sequence you push that value into environmental variables.
This solution is better when you are forced to reference the same property multiple times (e.g: Thymeleaf templates) and they are read at runtime.
A slight disadvantage is that the property won't show up in the config files, unless you initialize them with a default value. So you have to find other ways of documenting it.
The major disadvantage still is that there's no guarantee that @Value("...") will initialize after you've set the environmental property. The Autowiring solution overcomes this problem by forcing the initialization order. But with this one you only need
@Autowired
private AppConfig config;
to cause them to be initialized after, you could also use @DependsOn and you don't need to use a getter.
In my opinion there needs to be a better way to do this, random properties being stateless makes them impractical for anything other than testing. But they certainly don't want doing it considering they have created so many workarounds for assigning and reading back random port numbers.
Solution 5:[5]
After trying different Methods this was the one working in all cases.
public static void main(String[] args) {
System.setProperty("APP_UUID", UUID.randomUUID().toString());
SpringApplication app = new SpringApplication(Restdemo1Application.class);
}
Then reference it in the config
info:
app:
uuid: ${APP_UUID}
spring:
cloud:
stream:
binders:
solace-broker:
environment:
solace:
java:
client-name: ${info.app.name:-}/${spring.profiles.active}/${info.app.uuid}
The approach with:
#{T(com.yourdomain.config.TestConfig).RANDOM_LONG}
and
"#{systemProperties['APP_UUID'] = (systemProperties['APP_UUID'] ?: '${random.uuid}') }"
did not work for be because some frameworks (for example spring cloud stream with solace) do not evaluate the custom expressions.
Solution 6:[6]
You can inject directly a UUID to your .properties file:
service.uuid=#{T(java.util.UUID).randomUUID().toString()}
This technique can be used on environments without Spring Boot.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | mister.elastic |
| Solution 2 | Dmytro Verbivskyi |
| Solution 3 | |
| Solution 4 | Balázs Eszes |
| Solution 5 | helios |
| Solution 6 | Archimedes Trajano |
