Invoke multiple ConditionalOnProperties in the YAML file - spring

I couldn't find the solution in any other questions posted here.
I have multiple beans with conditionalOnProperty invoking different beans like below
#Bean
#ConditionalOnProperty(name = 'invoke.bean', havingValue = 'bean1', matchIfMissing = true)
SupportingInterface bean1() {
return new Bean1()
}
#Bean
#ConditionalOnProperty(name = 'invoke.bean', havingValue = 'bean2')
SupportingInterface bean2() {
return new Bean2()
}
in the yml file if I do invoke.bean: bean1 or invoke.bean: bean2 individually works fine but if I want to use both invoke.bean: bean1, bean2 how can I do it?

Related

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

Excluding Configuration class from SpringBootTest

I have a class to configure Kafka under src/main/java:
#Configuration
public class SenderConfig {
#Value("${spring.kafka.producer.bootstrap-servers}")
private String bootstrapServers;
#SuppressWarnings({ "unchecked", "rawtypes" })
#Bean
public ProducerFactory<String,Item> producerFactory(){
log.info("Generating configuration to Kafka key and value");
Map<String,Object> config = new HashMap<>();
config.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,bootstrapServers);
config.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
config.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);
return new DefaultKafkaProducerFactory(config);
}
I have a class under src/test/java to test a repository and I want to exclude this configuration class:
#SpringBootTest(properties = { "spring.cloud.config.enabled=false",
"spring.autoconfigure.exclude=com.xyz.xyz.config.SenderConfig" })
#Sql({ "/import_cpo_workflow.sql" })
public class WorkflowServiceTest {
#Autowired
private WorkflowRep workflowRep;
#Test
public void testLoadDataForTestClass() {
assertEquals(1, workflowRep.findAll().size());
}
}
Error: Caused by: java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes: com.xyz.xyz.config.SenderConfig
How can I exclude this configuration class from my test since I'm not testing Kafka in this moment?
You can declare a SenderConfig property in the test class, annotated as #MockBean (and do nothing with it if you don't need it in the test) and that will effectively override the real one in the test's ApplicationContext and stop the real one from being instantiated by the BeanFactory.
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/test/mock/mockito/MockBean.html
Try to use #ComponentScan to exclude classes.
Example:
#ComponentScan(basePackages = {"package1","package2"},
excludeFilters = {#ComponentScan.Filter(
type = FilterType.ASSIGNABLE_TYPE,
value = {SenderConfig.class})
})

How to dynamicly create bean at application start with properties in Spring Boot

I use multiple databases to store different entities. My entities and repos are splited in different packeges. And for each database I need to create #Configuration to persist data properly and create tables properly.
Here is #Configuration file for one of me databases
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactory",
basePackages = { "com.domain.shop.users.repositories" },
transactionManagerRef = "transactionManager"
)
public class UsersDatabaseConfig {
#Autowired
private DatasourceConnectionManager dscm;
#Primary
#Bean(name = "dataSource1")
public DataSource dataSource() {
return dscm.getDataSource("users");
}
#Primary
#Bean(name = "entityManagerFactory")
public LocalContainerEntityManagerFactoryBean
entityManagerFactory(EntityManagerFactoryBuilder builder,
#Qualifier("dataSource1") DataSource dataSource1) {
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", "update");
return builder
.dataSource(dataSource1)
.packages("com.domain.shop.users.models")
.properties(properties)
.build();
}
#Primary
#Bean(name = "transactionManager")
public PlatformTransactionManager
transactionManager(#Qualifier("entityManagerFactory") EntityManagerFactory
entityManagerFactory) {
return new JpaTransactionManager(entityManagerFactory);
}
}
It works pretty fine! But I need to create separate class for each database
I'd like to create such beans at application start reading properties .yml file.
And look at annotations on top - how to pass some parameters to annotations?
Other words, I have .yml file with database connections properties. I want to add some property to each database (like, rootdirectory = com.domain.shop.products). After that I want to create dynamic bean with following code:
#Configuration
#EnableTransactionManagement
#EnableJpaRepositories(
entityManagerFactoryRef = "productsEntityManagerFactory",
basePackages = { "com.domain.shop.products.repositories" },
transactionManagerRef = "productsTransactionManager"
)
and next
#Bean(name = "productsDataSource")
public DataSource dataSource() {
return dscm.getDataSource("products");
}
You can use #Profile annotation. And then create properties file for every profile. E.g.
#Profile("test")
#Profile("dev")
application-test.yml
application-dev.yml
Use #ConditionalOnExpression will load the `#Configuration class if expression validates to true
#ConditionalExpression("${my.rest.controller.enabled}")
or use #ConditionalOnProperty
#ConditionalOnProperty(prefix = "spring", name = "example.values")

#ConditionalOnProperty not reflected when viewing #Autowired beans in Intellij IDEA

Let's say I have #Configuration class which registers bean of type RestClient conditionally using #ConditionalOnProperty.
#Configuration
public class RestClientConfig {
#Bean("restClient")
#ConditionalOnProperty(prefix = "rest.client", name = "enabled", havingValue = "false", matchIfMissing = true)
public RestClient restClient(RestProperties properties) {
return new HttpRestClient(...);
}
#Bean("restClient")
#ConditionalOnProperty(prefix = "rest.client", name = "enabled", havingValue = "true")
public RestClient mockRestClient(RestProperties properties) {
return new MockRestClient();
}
}
When I run this application, everything works. Implementation of given type is chosen correctly when I autowire RestClient in another bean.
However, when I view this setup in Intellij IDEA, it reports:
Is there a way to instruct Intellij to know about #ConditionalOnProperty or do it in different way in Spring Boot?

Resources