Use #Value when defining custom #Conditional - spring

I would like to create custom condition that will be pass as value to #Conditional interface applied on #Configuration class. Basically I would like to create beans from Config1 or Config2 depending on a configuration that is stored in a database - dbConfig.get('configType') connects to the database and returns the value. However it looks that dbConfig is not created at that time yet. What could be a way to resolve it? I would like to avoid if-else within a bean definition to differentiate creation code.
#Configuration
#Conditional(OnCondition1.class)
public class Config1{
// beans definitions
}
#Configuration
#Conditional(OnCondition2.class)
public class Config2{
// beans definitions
}
public class OnCondition1 implements Condition{
#Value("#{dbConfig.get('configType')}")
private String configType;
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return configType.equals('1');
}
}
public class OnCondition2 implements Condition{
#Value("#{dbConfig.get('configType')}")
private String configType;
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
return configType.equals('2');
}
}

Related

How to get list of specific implementation based on the condition at runtime

I have 2 interfaces with a list of implementations for each interface like this.
Now I want to call any interface implementations on runtime based on the user input. For e.g. if the user selects file then FileAImpl, FileBImpl, FileCImpl should be executed.
So I created a parent interface
And added conditions for each case
public class IsFileCondition implements Condition {
#Override
public boolean matches(final ConditionContext context, final AnnotatedTypeMetadata metadata) {
return ...;
}
}
And then created a configuration
#Configuration
#AllArgsConstructor
public class CustomBeanService {
private final List<FileInterface> fileInterface;
private final List<DbInterface> dbInterface;
#Bean
#Conditional(IsFileCondition.class)
public List<? extends ConverterInterface> fileInterface() {
return fileInterface;
}
#Bean
#Conditional(IsProdEnvCondition.class)
public List<? extends ConverterInterface> dbInterface() {
return dbInterface;
}
}
And then use it like this
#SpringBootApplication
#Autowired
private List<? extends ConverterInterface> convert;
But the list of convert here gives me all the implementation and not the specific selection.

how can application yaml value inject at runtime in spring boot?

I want to change the value of application.yaml at loading time.
ex) application.yaml
user.name: ${name}
Here, I want to put this value by calling an external API such as a vault, rather than a program argument when the jar is executed with the name value.
First of all, I think I need to write code that implements EnvironmentPostProcessor and calls external API, but I don't know how to inject that value. can I get help?
public class EnvironmentConfig implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
// API CAll
// how can inject yaml value??
}
}
I don't know which way to orient myself.
OPTION 1: doing it via EnvironmentPostProcessor:
assuming you have registered you EnvironmentPostProcessor in /resources/META-INF/spring.factories file:
org.springframework.boot.env.EnvironmentPostProcessor=package.to.environment.config.EnvironmentConfig
all you need is to add your custom PropertySource:
public class EnvironmentConfig implements EnvironmentPostProcessor {
#Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
SpringApplication application) {
environment.getPropertySources()
.addFirst(new CustomPropertySource("customPropertySource"));
}
}
public class CustomPropertySource extends PropertySource<String> {
public CustomPropertySource(String name) {
super(name);
}
#Override
public Object getProperty(String name) {
if (name.equals("name")) {
return "MY CUSTOM RUNTIME VALUE";
}
return null;
}
}
OPTION 2: doing it via PropertySourcesPlaceholderConfigurer:
A class that is responsible for resolving these palceholders is a BeanPostProcessor called PropertySourcesPlaceholderConfigurer (see here).
So you could override it and provide you custom PropertySource that would resolve your needed property like so:
#Component
public class CustomConfigurer extends PropertySourcesPlaceholderConfigurer {
#Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, ConfigurablePropertyResolver propertyResolver) throws BeansException {
((ConfigurableEnvironment) beanFactoryToProcess.getBean("environment"))
.getPropertySources()
.addFirst(new CustomPropertySource("customPropertySource"));
super.processProperties(beanFactoryToProcess, propertyResolver);
}
}
use ConfigurationProperties for your properties and change it via an api like this:
#Component
#ConfigurationProperties(prefix = "user")
public class AppProperties {
private String name;
//getter and setter
}
#RestController
public class AppPropertiesController {
#Autowire
AppProperties prop;
#PostMapping("/changeProp/{name}")
public void change(#PathVariable String name){
prop.setName(name);
}
}

Unable to read application.yml in Condition class

I have this class
#Service
#Conditional(MyCondition.class)
public class Class1 implements AcknowledgingMessageListener<String, GenericRecord> {
#Value("#{'${audit.module}'}")
private String xyz;
Here is MyCondition
#Configuration
#Component
#EnableConfigurationProperties
#ConfigurationProperties("audit")
public class MyCondition implements Condition {
#Value("#{'${audit.module}'}")
private String ss;
}
The problem is in MyCondition #Value is unable to read the value from from application.yml.
MyCondition is not a spring managed bean when you use it as a #Conditional. So you can not use #Value inside MyCondition. However there are some workaround.
You can set String ss as static and add one setter value with #Value.
#Component
public class MyCondition implements Condition {
private static String ss;
#Value("${audit.module}")
public void setSs(String ss) {
MyCondition.ss = ss;
}
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
But this value will be set after Class1 has been created, so at the time on validating #Conditional ss will be null.
But I think you will need to use the value of ss inside the matches method. So inside the matches method you can access property values with ConditionContext
public class MyCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
System.out.println(context.getEnvironment().getProperty("audit.module"));
return true;
}
}

Spring Boot #Conditional annotation gets ignored

I am trying to enable scheduler based on certain properties (Condition) however it ignores my #Conditional annotation irrespective condition outcome. Any suggestions?
Conditional Class
public class SchedulerCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return property#1 || property#2
}
}
Configuration Class
#Configuration
public class Scheduler {
#Conditional(SchedulerCondition.class)
#Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public void processJobs() {
......
}
}

Spring MVC instantiate object on request attribute

Assume I am creating a PrinterService class that has a AbstractPrinter object. AbstractPrinter is subclassed by classes such as HPPrinter, FilePrinter etc.
The exact kind of printer object to be used is mentioned in the RequestParam object passed to my Controller (it is a request attribute).
Is there any way I can inject the right kind of concrete printer class using Spring?
All the other dependencies are injected using #Autowired annotation. How to inject this one?
You can create and load a factory of AbstractPrinter objects during container startup as shown below and dynamically call the respective the AbstractPrinter's print() (or your own method) based on the input parameter (comes from controller) to the service.
In the below code for PrinterServiceImpl class, the main point is that all of the List<AbstractPrinter> will be injected by Spring container (depends upon how many implementation classes you provide like HPPrinter, etc..). Then you will load those beans into a Map during container startup with printerType as key.
#Controller
public class YourController {
#Autowired
private PrinterService printerService;
public X myMethod(#RequestParam("input") String input) {
printerService.myServiceMethod(input);
//return X
}
}
PrinterServiceImpl class:
public class PrinterServiceImpl implements PrinterService {
#Autowired
private List<AbstractPrinter> abstractPrinters;
private static final Map<String,AbstractPrinter> myPrinters = new HashMap<>();
#PostConstruct
public void loadPrinters() {
for(AbstractPrinter printer : abstractPrinters) {
myPrinters.put(printer.getPrinterType(), printer);
}
}
//Add your other Autowired dependencies here
#Override
public void myServiceMethod(String input){//get input from controller
AbstractPrinter abstractPrinter= myPrinters.get(input);
abstractPrinter.print();//dynamically calls print() depending on input
}
}
HPPrinter class:
#Component
public class HPPrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "HP";
}
#Override
public void print() {
// Your print code
}
}
FilePrinter class:
#Component
public class FilePrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "FILE";
}
#Override
public void print() {
// Your print code
}
}
You could create a dedicated PrinterService instance per AbstractPrinter concrete class. For example you could achieve this using Spring configuration which follow the factory pattern:
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private HPPrinter hpPrinter;
#Autowired
private FilePrinter filePrinter;
#Bean
public PrinterService hpPrinterService() {
return new PrinterService(hpPrinter);
}
#Bean
public PrinterService filePrinterService() {
return new PrinterService(filePrinter);
}
public PrinterService findPrinterService(PrinterType type){
if (type == HP)
return hpPrinterService();
....
}
}
Then in your controller, inject PrinterServiceConfiguration then call findPrinterService with the right printer type.
Don't forget to add PrinterServiceConfiguration at your configuration #Import.
If the list of printer is dynamic you could switch to prototype bean :
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private List<AbstractPrinter> printers;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrinterService createPrinterService(PrinterType type){
return new PrinterService(findPrinterByType(type));
}
private Printer findPrinterByType(PrinterType type) {
// iterate over printers then return the printer that match type
// throw exception if no printer found
}
}

Resources