make ConfigurationProperties optional - spring

I have spring boot application which uses application.yml for configuration.
The structure is something like:
...
rmq:
host: "host"
port: 5672
...
In my code I have ApplicationConfig class which looks like below:
#AllArgsConstructor
class ApplicationConfig {
private RabbitConfig rabbitConfig;
}
#ConfigurationProperties(prefix = "rmq")
class RabbitConfig {
#NotNull
private String host;
#NotNull
private Integer port;
}
The problem is that section rmq is optional in my application.
And I want field rabbitConfig will init by null in case it is absent in application.yml.
But really if I drop rmq section in config file I've got an error (rmq.host is absent).
Is it possible to force springboot to init rabbitConfig with null in that case?

You can use #ConditionalOnProperty for such cases. In your case, you should define it as:
#ConfigurationProperties(prefix = "rmq")
#ConditionalOnProperty(prefix = "rmq", name = "host", matchIfMissing = true)
class RabbitConfig {
#NotNull
private String host;
#NotNull
private Integer port;
}
Then it will load the bean only if there is rmq.host set, and if not, it will be set to null.
There is also an alternative that you always put i.e. rmq.enabled = true | false, and then #ConditionalOnProperty(prefix = "rmq", name = "host", havingValue = true)

Related

Why SpringBoot validation not work with single #PropertySource

My Source code:
Person.java
#Component
#Validated
#PropertySource(value = "classpath:person.yaml")
#Data
public class Person {
#NotNull(message = "test can't be null")
private String test;
#Value("${name}")
private String name;
#Autowired
private Cat pet;
#Max(value=120,message="invalid age")
#Value("${age}")
private int age;
#Email(message = "invalid email address")
#Value("${email}")
private String email;
}
person.yaml
name: "test"
email: "abcdefg"
age: 11111
I use my Person class on SpringBootApplication with invalid email address and age, but it ran successfully without any validation exception.
But If I replace the #PropertySource(value = "classpath:person.yaml") to #ConfigurationProperties(prefix = "person") or use both #PropertySource and #ConfigurationProperties
application.yaml
person:
name: "test"
email: "abcdefg"
age: 11111
My SpringBoot throw an validation exception successfully.
From #PropertySource documantation:
Annotation providing a convenient and declarative mechanism for adding a PropertySource to Spring's Environment. To be used in conjunction with #Configuration classes.
You don't have #Configuration anotation under class declaration.
Your second example has #ConfigurationProperties annotation and this is from documentation:
Annotation for externalized configuration. Add this to a class definition or a #Bean method in a #Configuration class if you want to bind and validate some external Properties (e.g. from a .properties file).
In a nutshell: #PropertySource need #Configuration but #ConfigurationProperties not.

What is the best way to select bean implementation from application.yaml

I have a spring boot application in which I want to Autowire a bean for which implementation is specified in application.yaml. What is the best way to achieve it?
#Component
public class FooFormatter implements Formatter {}
#Component
public class BarFormatter implements Formatter {}
public class MyService {
#Autowired
#Qualifier("value_from_config")// The implementation is specified in application.yaml file
private Formatter formatter;
}
The best way to achieve it is to use #ConditionalOnProperty.
So given the followings :
#Component
#ConditionalOnProperty(prefix = "app.formatter", name = "impl", havingValue = "foo",matchIfMissing = true)
public class FooFormatter implements Formatter {
}
#Component
#ConditionalOnProperty(prefix = "app.formatter", name = "impl", havingValue = "bar")
public class BarFormatter implements Formatter {
}
Then to enable FooFormatter only , configure the application properties as :
app.formatter.impl=foo
To enable BarFormatter only , configure the application properties as :
app.formatter.impl=bar
If no app.formatter.impl is defined in application properties , it will default to FooFormatter (because of the matchIfMissing = true)

Spring Boot: use autowired constructor with class from configuration file

I have a Spring Boot 2.3 application with a controller:
#RestController
public class StatusController {
private final ServerStatusCheck serverStatusCheck;
private final ServerStatusMapper serverStatusMapper;
#Autowired
public StatusController(AService aService, ServerStatusMapper serverStatusMapper) {
this.serverStatusCheck = aService;
this.serverStatusMapper = serverStatusMapper;
}
// (...)
}
The class AService implements the interface ServerStatusCheck. There is also a BService class, also implementing ServerStatusCheck interface.
What I need to do: the injected AService object should be configurable in a configuration file, so that the service injected is either "AService" or "BService", depending on the configuration file values. What is the best way to achieve this using Spring Boot? If possible, I would like to keep the constructor-based autowiring (instead of field-based autowiring).
You can create the different beans in a configuration class with condition like https://reflectoring.io/spring-boot-conditionals/
#Configuration
public class ServiceConfiguration {
#ConditionalOnProperty(value="service.a.enabled", havingValue = "true", matchIfMissing = true)
public ServerStatusCheck serverStatusCheckA() {
return new AService();
}
#ConditionalOnMissingBean
#ConditionalOnProperty(value="service.b.enabled", havingValue = "true", matchIfMissing = true)
public ServerStatusCheck serverStatusCheckB() {
return new BService();
}
}
and then wire the bean into the constructor

SpringBoot Failed to bind properties under app

I have a SpringBoot 2.1.7.RELEASE project with gradle. I'm getting an error when I try to use #ConfigurationProperties
The property that I'm trying to bind is existing in my application-default.properties and if I run the project using Itellij I can see that the property is ingested in my component.
If I enable #EnableConfigurationProperties I got an error.
My application-default.properties
app.forwarding-endpoint=localhost:8080
My AppProperties.java
#ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
#Validated
#Data
public class AppProperties {
#NotBlank
#Pattern(regexp = "^(.+):\\d+$")
private String forwardingEndpoint;
}
My Application.java
#SpringBootApplication
#EnableConfigurationProperties(AppProperties.class)
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application .class, args);
}
}
My component that is using the property:
public MyComponent(#Value("${app.forwarding-endpoint}") String forwardingEndpoint) {
log.info("Forwarding endpoint {}", forwardingEndpoint);
}
The error that I get is:
Binding to target org.springframework.boot.context.properties.bind.BindException: Failed to bind properties under 'app' to com.config.AppProperties failed:
Property: app.forwardingEndpoint
Value: null
Reason: must not be blank
What am I missing?
The cause is in the order of initialization.
You did not fill AppProperties but start to use it in components. You need to annotate this class also as a component but it's not a good approach from point of view of an architecture.
The concept of #ConfigurationProperties is quite raw for Spring and without some manipulations, you will quite difficult to force it to work correctly. I propose a simple 'trick' (or 'another approach'):
#Data
public class AppProperties {
#NotBlank
#Pattern(regexp = "^(.+):\\d+$")
private String forwardingEndpoint;
}
(I think the place of #validated is not in the entitity/DO).
And place in your #Configuration next code:
#Bean
#ConfigurationProperties(prefix = "app", ignoreUnknownFields = false)
public AppProperties setAppProperties() {
return new AppProperties();
}
And next, you can inject AppProperties bean in any component.

Instantiate a service according to a property on Spring Boot

A good practice is defining a service as an interface and its implementation on a class.
Assuming I have 2 classes which implement the same interface, and I'd like to differentiate them according a property (not to a profile). I mean, if I have #Autowire private MyServiceInterface myService; I'd like to receive an instance of PotatoServiceImpl if I have myproperty=potato or an instance of TomatoServiceImpl if I have myproperty=tomato.
I'm not using profiles.
P.S. When I say a property,I mean a property in application.properties
Look:
public interface MyInterface {
}
#Component
#ConditionalOnProperty(prefix = "myproperty" havingValue = "potato", matchIfMissing = false)
public class MyPotatoImpl implements MyInterface {
}
#Component
#ConditionalOnProperty(prefix = "myproperty" havingValue = "tomato", matchIfMissing = false)
public class MyTomatoImpl implements Myinterface {
}
#Component
public class Consumer {
#Autowire
private MyInterface tomatoOrPotato; //depending on property myproperty value
}
This is for me a very elegant solution to implement the strategy creational design pattern spring styled.
Look here for docs about #ConditionalOnProperty annotation.

Resources