简体   繁体   中英

How to autowire a bean inside a Spring @Condition class

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.

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

Check this link https://stackoverflow.com/a/70351394/7237884 here.

The docs say

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.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM