简体   繁体   中英

Spring boot - use a configurable value on a @Qualifier

I have two implementations for one interface, i want to choose what implementation to use based on a configuration. The qualifier solution did not work as it is initialized prior to the configuration. How can I achieve this?

I've got your comment:

I have two different implementations of a job and it is possible to change the type every month so if configurable, less deployments and code changes are made.

You might have something like this:

 interface Job {
     void foo();
 }

 class JobA implements Job {
     void foo() {...}
 }

 class JobB implements Job {
     void foo() {...}
 }

 class JobExecutor {
    
    Job job;
    // autowired constructor
    public JobExecutor(Job job) {this.job = job;}
 }

And, if I got you right, it doesn't make sense to load two beans simultaneously in the same application context.

But if so, then @Qualifier is not a right tool for the job.

I suggest using conditions that are integrated into spring boot instead:

@Configuration
public class MyConfiguration {

    @ConditionalOnProperty(name = "job.name", havingValue = "jobA")
    @Bean 
    public Job jobA() {
         return new JobA();
    }

    @ConditionalOnProperty(name = "job.name", havingValue = "jobB")
    @Bean 
    public Job jobB() {
         return new JobB();
    }
    @Bean
    public JobExecutor jobExecutor(Job job) {
       return new JobExecutor(job);
    }
}

Now in application.properties (or yaml whatever you have) define:

 job.name = jobA # or jobB

Of course, instead of jobA/jobB you might use more self-explanatory names from your business domain.

You could pull it off with if you fiddle around with Spring java-based config a bit, where you programmatically decide the right implementation based on a config value:

@Configuration
public class MyAppContext implements EnvironmentAware{

    private Environment env;

    @Override
    public void setEnvironment(final Environment env) {
       this.env = env;
    }

    @Bean
    public MyBeanByConfig myBeanByConfig(){
        String configValue = env.getProperty("mybean.config");

        if(configValue.equals("1")){
           return new MyBeanByConfigOne();
        }else{
           return new MyBeanByConfigTwo();
        }
    }
}

and on the qualifier you would put:

@Qualifier("myBeanByConfig")

you may need to add @ComponentScan and @PropertySource on the configuration class also.

Let's suppose you have an interface:

public interface Foo {
    void doSomething();
}

And 2 implementations:

public class Foo_A implements Foo {
    @Override
    doSomething() {...}
}

public class Foo_B implements Foo {
    @Override
    doSomething() {...}
}

Now you want to use Foo_A/Foo_B depending on a property value in your properties file:

foo_name: "A"

The simplest way I found to do this:

  1. First, you qualify your implementations

@Component("Foo_A")
public class Foo_A implements Foo {
    @Override
    doSomething() {...}
}

@Component("Foo_B")
public class Foo_B implements Foo {
    @Override
    doSomething() {...}
}

  1. Then, wherever you gonna use this (class Bar, for example), you can just use the @Qualifier to specify the implementations you are instantiating and get the value from the property with the @Value. Then, inside the method, with a simple if/else statement, you use the property value to decide which implementation you're going to call.
public class Bar {

    @Value("${foo_name}")
    private String fooName;

    @Qualifier("Foo_A")
    private Foo fooA;

    @Qualifier("Foo_B")
    private Foo fooB;

    public void doSomething() {
        if (fooName.equals("A")) {
            fooA.doSomething();
        } else {
            fooB.doSomething();
        }
    }

}

I ended up adding to the main app class the two implementations autowired then define a bean for each:

@Autowired
TypeOneImpl typeOneImpl
@Bean(name = "typeOneImpl")
public InterfaceRClass getTypeOneImpl()
{
    return typeOneImpl;
}

Then in the other class I defined a config field

@Value("${myClass.type}")
private String configClassType;
// the below should be defined in constructor
private final InterfaceRClass interfaceRClassElement ;

And added a setter for it with @Autowired annotation:

@Autowired
public void setMyClassType(ApplicationContext context) {
    interfaceRClassElement = (InterfaceRClass) context.getBean(configClassType);
}

In configuration, the value should be typeOneImpl (typeTwoImpl is added for an additional implementation)

knowing that IOC works fine throught constructor maybe I would do something like this:

@Component
public class JobExecutor implements WhateverService {


    private final Job myJob;

    public JobExecutor(Map<String, Job> allJobImpls,
                       @Value("${myClass.type}") final String classType) {

        this.myJob = allImpls.get(classType);
    }

    public X myAction() {
         // some code
         return myJob.whatever(); //for example
    }
}

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