Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Immutable @ConfigurationProperties

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:

  1. Creating a @Bean of the MyProps class with two constructors
    • Providing two constructors: empty and with neededProperty argument
    • The bean is created with new MyProps()
    • Results in the field being null
  2. Using @ComponentScan and @Component to provide the MyProps bean.
    • Results in BeanInstantiationException -> NoSuchMethodException: MyProps.<init>()

The only way I have got it working is by providing getter/setter for each non-final field.

like image 480
RJo Avatar asked Oct 01 '14 09:10

RJo


People also ask

What is @ConstructorBinding?

@ConstructorBinding. . This annotation tells Spring to bind our configuration properties using the provided constructor instead of using the setters.

What makes a class immutable?

Immutable class means once the object of the class is created its fields cannot be modified or changed. In Java, all the wrapper classes like Boolean, Short, Integer, Long, Float, Double, Byte, Char, and String classes are immutable classes.

What is @ConfigurationProperties?

@ConfigurationProperties allows to map the entire Properties and Yaml files into an object easily. It also allows to validate properties with JSR-303 bean validation. By default, the annotation reads from the application.


2 Answers

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() { .. } } 
like image 135
davidxxx Avatar answered Sep 28 '22 21:09

davidxxx


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

like image 39
Tom Avatar answered Sep 28 '22 20:09

Tom