Reputation: 16291
Is it possible to have immutable (final) fields with Spring Boot's @ConfigurationProperties
annotation? Example below
@ConfigurationProperties(prefix = "example")
public final class MyProps {
private final String neededProperty;
public MyProps(String neededProperty) {
this.neededProperty = neededProperty;
}
public String getNeededProperty() { .. }
}
Approaches I've tried so far:
@Bean
of the MyProps
class with two constructors
neededProperty
argumentnew MyProps()
null
@ComponentScan
and @Component
to provide the MyProps
bean.
BeanInstantiationException
-> NoSuchMethodException: MyProps.<init>()
The only way I have got it working is by providing getter/setter for each non-final field.
Upvotes: 67
Views: 36312
Reputation: 629
If you want to slice test your properties in your application without loading the whole spring boot context use the @EnableConfigurationProperties
in your test.
Example:
src/main/resources/application.yml
myApp:
enabled: true
name: "test"
@Getter
@AllArgsConstructor
@Configuration
@ConfigurationProperties(prefix = "myApp")
public class MyApplicationProperties {
boolean enabled;
String name;
}
// this will only load MyApplicationProperties.class in spring boot context making it fast
@SpringBootTest( classes = MyApplicationProperties.class})
@EnableConfigurationProperties
class MyApplicationPropertiesTest {
@Autowired
MyApplicationProperties myApplicationProperties ;
@Test
void test_myApplicationProperties () {
assertThat(myApplicationProperties.getEnabled()).isTrue();
assertThat(myApplicationProperties.getName()).isEqualTo("test");
}
Upvotes: 0
Reputation: 5448
Just an update on the latest support of more recent Spring-Boot versions:
If you're using a jdk version >= 14, you can use record
type which does more or less the same as the Lombok version but without Lombok.
@ConfigurationProperties(prefix = "example")
public record MyProps(String neededProperty) {
}
You can also use record inside the MyProps
record to manage nested properties. You can see an example here.
Another interesting post here which shows that the @ConstructorBinding
annotation is even not necessary anymore if only one constructor is declared.
Upvotes: 3
Reputation: 131346
From Spring Boot 2.2, it is at last possible to define an immutable class decorated with @ConfigurationProperties
.
The documentation shows an example.
You just need to declare a constructor with the fields to bind (instead of the setter way) and to add the @ConstructorBinding
annotation at the class level to indicate that constructor binding should be used.
So your actual code without any setter is now fine :
@ConstructorBinding
@ConfigurationProperties(prefix = "example")
public final class MyProps {
private final String neededProperty;
public MyProps(String neededProperty) {
this.neededProperty = neededProperty;
}
public String getNeededProperty() { .. }
}
Upvotes: 54
Reputation: 93
Using similar approach to the one from https://stackoverflow.com/a/60442151/11770752
But instead of AllArgsConstructor
you can use the RequiredArgsConstructor
.
Consider following applications.properties
myprops.example.firstName=Peter
myprops.example.last-name=Pan
myprops.example.age=28
Note: Use consistency with your properties, i just wanted to show-case that both were correct (fistName
and last-name
).
Java Class pickping up the properties
@Getter
@ConstructorBinding
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "myprops.example")
public class StageConfig
{
private final String firstName;
private final Integer lastName;
private final Integer age;
// ...
}
Additionally you have to add to your build-tool a dependency.
build.gradle
annotationProcessor('org.springframework.boot:spring-boot-configuration-processor')
or
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>${spring.boot.version}</version>
</dependency>
If you take it one step further to provide nice and precise descriptions for you configurations, consider creating a file additional-spring-configuration-metadata.json
in directory src/main/resources/META-INF
.
{
"properties": [
{
"name": "myprops.example.firstName",
"type": "java.lang.String",
"description": "First name of the product owner from this web-service."
},
{
"name": "myprops.example.lastName",
"type": "java.lang.String",
"description": "Last name of the product owner from this web-service."
},
{
"name": "myprops.example.age",
"type": "java.lang.Integer",
"description": "Current age of this web-service, since development started."
}
}
(clean & compile to take effect)
At least in IntelliJ, when you hover over the properties inside application.propoerties
, you get a clear despriction of your custom properties. Very useful for other developers.
This is giving me a nice and concise structure of my properties, which i am using in my service with spring.
Upvotes: 2
Reputation: 144
Using Lombok annotations the code would looks like this:
@ConfigurationProperties(prefix = "example")
@AllArgsConstructor
@Getter
@ConstructorBinding
public final class MyProps {
private final String neededProperty;
}
Additionally if you want to Autowire this property class directly and not using @Configuration
class and @EnableConfigurationProperties
, you need to add @ConfigurationPropertiesScan
to main application class that is annotated with @SpringBootApplication
.
See related documentation here: https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-constructor-binding
Upvotes: 0
Reputation: 127
My idea is to encapsulate property groups via inner classes and expose interfaces with getters only.
Properties file:
myapp.security.token-duration=30m
myapp.security.expired-tokens-check-interval=5m
myapp.scheduler.pool-size=2
Code:
@Component
@ConfigurationProperties("myapp")
@Validated
public class ApplicationProperties
{
private final Security security = new Security();
private final Scheduler scheduler = new Scheduler();
public interface SecurityProperties
{
Duration getTokenDuration();
Duration getExpiredTokensCheckInterval();
}
public interface SchedulerProperties
{
int getPoolSize();
}
static private class Security implements SecurityProperties
{
@DurationUnit(ChronoUnit.MINUTES)
private Duration tokenDuration = Duration.ofMinutes(30);
@DurationUnit(ChronoUnit.MINUTES)
private Duration expiredTokensCheckInterval = Duration.ofMinutes(10);
@Override
public Duration getTokenDuration()
{
return tokenDuration;
}
@Override
public Duration getExpiredTokensCheckInterval()
{
return expiredTokensCheckInterval;
}
public void setTokenDuration(Duration duration)
{
this.tokenDuration = duration;
}
public void setExpiredTokensCheckInterval(Duration duration)
{
this.expiredTokensCheckInterval = duration;
}
@Override
public String toString()
{
final StringBuffer sb = new StringBuffer("{ ");
sb.append("tokenDuration=").append(tokenDuration);
sb.append(", expiredTokensCheckInterval=").append(expiredTokensCheckInterval);
sb.append(" }");
return sb.toString();
}
}
static private class Scheduler implements SchedulerProperties
{
@Min(1)
@Max(5)
private int poolSize = 1;
@Override
public int getPoolSize()
{
return poolSize;
}
public void setPoolSize(int poolSize)
{
this.poolSize = poolSize;
}
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("{ ");
sb.append("poolSize=").append(poolSize);
sb.append(" }");
return sb.toString();
}
}
public SecurityProperties getSecurity() { return security; }
public SchedulerProperties getScheduler() { return scheduler; }
@Override
public String toString()
{
final StringBuilder sb = new StringBuilder("{ ");
sb.append("security=").append(security);
sb.append(", scheduler=").append(scheduler);
sb.append(" }");
return sb.toString();
}
}
Upvotes: 1
Reputation: 11723
You can set the field values through @Value
annotations. These can be placed directly on the fields and don't require any setters:
@Component
public final class MyProps {
@Value("${example.neededProperty}")
private final String neededProperty;
public String getNeededProperty() { .. }
}
The downside of this approach is:
Upvotes: -2
Reputation: 781
In the end, if you want an immutable object you can also "hack" the setter that is
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
public String setSomeProperty(String someProperty) {
if (someProperty == null) {
this.someProperty = someProperty;
}
}
}
Obviously if the property is not just a String, that is a mutable object, things are more complicated but that's another story.
Even better you can create a Configuration container
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private final List<MyConfiguration> configurations = new ArrayList<>();
public List<MyConfiguration> getConfigurations() {
return configurations
}
}
where now the configuration is a clas without
public class MyConfiguration {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
public String setSomeProperty(String someProperty) {
if (this.someProperty == null) {
this.someProperty = someProperty;
}
}
}
and application.yml as
myapp:
configurations:
- someProperty: one
- someProperty: two
- someProperty: other
Upvotes: 1
Reputation: 26829
I have to resolve that problem very often and I use a bit different approach, which allows me to use final
variables in a class.
First of all, I keep all my configuration in a single place (class), say, called ApplicationProperties
. That class has @ConfigurationProperties
annotation with a specific prefix. It is also listed in @EnableConfigurationProperties
annotation against configuration class (or main class).
Then I provide my ApplicationProperties
as a constructor argument and perform assignment to a final
field inside a constructor.
Example:
Main class:
@SpringBootApplication
@EnableConfigurationProperties(ApplicationProperties.class)
public class Application {
public static void main(String... args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
ApplicationProperties
class
@ConfigurationProperties(prefix = "myapp")
public class ApplicationProperties {
private String someProperty;
// ... other properties and getters
public String getSomeProperty() {
return someProperty;
}
}
And a class with final properties
@Service
public class SomeImplementation implements SomeInterface {
private final String someProperty;
@Autowired
public SomeImplementation(ApplicationProperties properties) {
this.someProperty = properties.getSomeProperty();
}
// ... other methods / properties
}
I prefer this approach for many different reasons e.g. if I have to setup more properties in a constructor, my list of constructor arguments is not "huge" as I always have one argument (ApplicationProperties
in my case); if there is a need to add more final
properties, my constructor stays the same (only one argument) - that may reduce number of changes elsewhere etc.
I hope that will help
Upvotes: 17