I have an interface IInterface.java
like below:
public interface IInterface {
void printIt();
}
And there are two implementation classes for this: ImplementationA.java
and ImplementationB.java
@Component
public class ImplementationA implements IInterface {
@Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
@Component
public class ImplementationB implements IInterface {
@Override
public void printIt() {
System.out.println("Inside ImplementationB");
}
}
Now I have a listener class, which has this IInterface
as a member:
@Component
@AllArgsConstructor
public class Listener {
IInterface iInterface;
public void doStuff(){
iInterface.printIt();
}
}
Now, my requirement is to inject either of ImplementationA.java
or ImplementationB.java
in the iInterface
member of Listener.java
based on certain condition.
After some research I started using the @Conditional
annotation.
I added two classes ConditionA.java
and ConditionB.java
:
public class ConditionA implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
public class ConditionB implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
And I also changed my implementation classes as below(added the Conditional
annotation):
@Component
@Conditional(ConditionA.class)
public class ImplementationA implements IInterface {
@Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
@Component
@Conditional(ConditionB.class)
public class ImplementationB implements IInterface {
@Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
This seems to work like a charm for me. Whichever implementation class I need to inject I just return true
from its corresponding Condition
class and return false
from rest of the implementation class's Condition
class.
However this next part is where I am facing the challenge:
So from the above solution, I was hardcoding the return true
or return false
from the matches
method of the corresponding Condition
class. What if I need to return a dynamic value based on another component.
Lets say I have a spring Component
class MyCustomConfig
which has a member customFlag
and if this member is set to true, we need to inject ImplementationA.class.
I had tried the below(made the class @Component and also autowired MyCustomConfig):
@Component
public class ConditionA implements Condition {
@Autowired
MyCustomConfig myCustomConfig;
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return myCustomConfig.getCustomFlag();
}
}
However this simply does not work. myCustomConfig is not autowired and I get a null pointer exception.
Could someone please help me with this.
CodePudding user response:
I think there is no chance for what you need with the use of implements Condition
.
If you inspect the documentation for interface Condition
which is what the class that you provide to @Conditional
should do, in your case ConditionA
and ConditionB
it says:
Conditions must follow the same restrictions as BeanFactoryPostProcessor and take care to never interact with bean instances. For more fine-grained control of conditions that interact with @Configuration beans consider implementing the ConfigurationCondition interface.
Maybe this answer would provide a workaround for you as it mentioned above in documentation that in the case that you want to interfere with other beans you should implement your own custom ConfigurationCondition.
Just check that the phase where this ConfigurationCondition will run will be after the beans that you need are registered
CodePudding user response:
I would register the bean using a configuration class and a @Bean
as follows:
@Configuration
public class ImplementationConfig {
@Bean
public IInterface implementation(MyCustomConfig myCustomConfig) {
if (myCustomConfig.getCustomFlag()) {
return new ImplementationA();
} else {
return new ImplementationB();
}
}
}
You simply drop the Spring-related annotations in both ImplementationA
and ImplementationB
:
public class ImplementationA implements IInterface {
@Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
public class ImplementationB implements IInterface {
@Override
public void printIt() {
System.out.println("Inside ImplementationA");
}
}
And finally, you can drop the Condition
classes.