To use @ConfigurationProperties
annotation one must create a class with getters and setters like that:
@ConfigurationProperties(prefix = "some")
public class PropertiesConfig {
private boolean debug;
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
}
But this leads to the situation when somebody is tempted to modify this value by calling:
@Autowire
private PropertiesConfig config;
//....
config.setDebug(true);
Is there a way to create @ConfigurationProperties
annotated classes without setters and external parser/reader classes?
From Spring Boot 2.2, it is at last possible to define immutable class decorated with @ConfigurationProperties
. Really thanks to Spring developers to improve continuously their framework.
The documentation shows an example.
You just need to declare a constructor with the fields to bind (instead of the setter way) :
@ConstructorBinding
@ConfigurationProperties(prefix = "some")
public class PropertiesConfig {
private boolean debug;
public AcmeProperties(boolean enabled) {
this.enabled = enabled;
}
public boolean isDebug() {
return debug;
}
}
Note 1 : you have to define one and only one single constructor with the parameters to bind :
In this setup one, and only one constructor must be defined with the list of properties that you wish to bind and not other properties than the ones in the constructor are bound.
Note 2 : a @DefaultValue
was introduced to define default values of an immutable property binding.
Default values can be specified using @DefaultValue and the same conversion service will be applied to coerce the String value to the target type of a missing property.
Here is a more detailed example get from the official documentation :
import java.net.InetAddress;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.DefaultValue;
import org.springframework.boot.context.properties.ConstructorBinding;
@ConstructorBinding
@ConfigurationProperties("acme")
public class AcmeProperties {
private final boolean enabled;
private final InetAddress remoteAddress;
private final Security security;
public AcmeProperties(boolean enabled, InetAddress remoteAddress, Security security) {
this.enabled = enabled;
this.remoteAddress = remoteAddress;
this.security = security;
}
public boolean isEnabled() { ... }
public InetAddress getRemoteAddress() { ... }
public Security getSecurity() { ... }
public static class Security {
private final String username;
private final String password;
private final List<String> roles;
public Security(String username, String password,
@DefaultValue("USER") List<String> roles) {
this.username = username;
this.password = password;
this.roles = roles;
}
public String getUsername() { ... }
public String getPassword() { ... }
public List<String> getRoles() { ... }
}
}
One approach with as little boilerplate code as possible would be using an interface with getters only
public interface AppProps {
String getNeededProperty();
}
and getting rid of boilerplate getters and setters in the implementation with the help of Lombok's @Getter
and @Setter
annotations :
@ConfigurationProperties(prefix = "props")
@Getter
@Setter
public class AppPropsImpl implements AppProps {
private String neededProperty;
}
Then, for the bean bo be accessible to other beans only by interface, one can, instead of marking it as @Component
or using @EnableConfigurationProperties(AppPropsImpl.class)
on the main application class, consider putting it into a configuration which will expose it by interface:
@Configuration
@EnableConfigurationProperties
public class PropsConfiguration {
@Bean
public AppProps appProps(){
return new AppPropsImpl();
}
}
Now this bean can be injected only by using an interface, and this makes the setters not available to other beans:
public class ApplicationLogicBean {
@Autowired
AppProps props;
public void method(){
log.info("Got " + props.getNeededProperty());
}
}
Tested with Spring Boot 1.5.3 and Lombok 1.16.16.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With