Immutable Property Binding
0 CommentsLast Updated on September 10, 2020 by jt
Introduction
In this article, we will look at Spring Framework’s support of immutable property binding.
We described Spring external configuration in this article and also provided a more detailed article about the Java bean properties binding. In this article, we will demonstrate constructor binding using the merchant account configuration in this article.
Constructor binding enables immutability of @ConfigurationProperties
annotated classes. Immutable property binding is recent addition to the Spring Framework, and is considered a best practice, since the bound values cannot be changed.
Constructor Binding
When we want our configuration properties to be bound without getting errors we must ensure that our Java
bean has setters. These setters are used by Spring to set the values provided in the external property sources. This is a requirement if our Spring Boot version is before 2.2.0.
Constructor binding is not supported in Spring Boot versions older than version 2.2.0.
Our sample Spring Boot application uses a version above 2.2.0. It will be possible to demonstrate constructor binding.
To enable constructor binding we use the annotation @ConstructorBinding
. This annotation tells Spring to bind our configuration properties using the provided constructor instead of using the setters.
Usage
This annotation can be applied at class level or directly on the constructor.
Class level
This annotation can be applied at class level id and only if there is an ambiguous constructor. If we switch to constructor binding our class will look like this.
@ConstructorBinding @ConfigurationProperties(prefix = "merchantaccount") public class MerchantAccount { private final String name; private final String username; private final String code; private final int number; private final String currency; public MerchantAccount(String name, String username, String code, int number, String currency) { this.name = name; this.username = username; this.code = code; this.number = number; this.currency = currency; } //getters }
Then our configuration file will look like this.
@SpringBootApplication @EnableConfigurationProperties({MerchantAccount.class}) public class MyApplication { public static void main(String[] args) { SpringApplication.run(MyApplication.class, args); } }
Constructor level
If our Java Bean has more than one constructor, then we cannot annotate at class/type level instead we must use @ConstructorBinding
directly on the constructor that should be bound. In our case, we have defined more than one constructor. We want our properties to be bound using the second constructor.
@ConfigurationProperties(prefix = "merchantaccount") public class MerchantAccount { private String name; private String username; private String code; private int number; private String currency; public MerchantAccount(String name, String username, String code, int number, String currency) { this.name = name; this.username = username; this.code = code; this.number = number; this.currency = currency; } @ConstructorBinding public MerchantAccount(String username, String code, int number, String currency) { this.username = username; this.code = code; this.number = number; this.currency = currency; } //getters }
Let us add an API key object to our Java Bean as a nested member. Spring bounds all the nested members through their constructors if constructor binding is used. Therefore we need to provide its constructor as well. Now our Java bean will look like this.
@ConfigurationProperties(prefix = "merchantaccount") public class MerchantAccount { private final String name; private final String username; private final String code; private final int number; private final String currency; Private final ApiKey apikey; @ConstructorBinding public MerchantAccount(String name, String username, String code, int number, String currency, ApiKey apikey) { this.name = name; this.username = username; this.code = code; this.number = number; this.currency = currency; this.apikey = apikey; } public static class ApiKey { private final String key; private final String type; public ApiKey(String key, String type) { this.key = key; this.type = type; } } //getters and setters }
Enabling @ConstructorBinding
To enable this annotation we must use either @EnableConfigurationProperties
or the @EnableConfigurationPropertiesScan
. We cannot use it with other enablers such as @Bean
or @Component
or beans loaded with @Import
.
Conclusion
We have touched base about the immutability of property binding. If constructor binding is not used then the setters will be used. However, constructor binding ensures that all the property fields are final thus no need for providing setters.