5

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?

user666
  • 1,750
  • 3
  • 18
  • 34
  • does it work without the qualifier? – Jose Martinez Aug 06 '19 at 12:21
  • Could you please describe your use-case? Qualifiers are used when there are more than one bean of the same type co-exist in Application context and you want to inject a particular bean. In this case no need for dynamic configuration usually, you chose among a set of predefined values for injecting the bean with qualifier – Mark Bramnik Aug 06 '19 at 12:24
  • @JoseMartinez Without it, i will have to add constructor dependencies manually, which is not nice in a spring project – user666 Aug 06 '19 at 12:26
  • @MarkBramnik 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. – user666 Aug 06 '19 at 12:27
  • Possible duplicate of [How to read Qualifier from property file in spring boot?](https://stackoverflow.com/questions/50208018/how-to-read-qualifier-from-property-file-in-spring-boot) – Andy Brown Aug 07 '19 at 07:54

5 Answers5

14

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.

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
4

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.

Maciej Kowalski
  • 25,605
  • 12
  • 54
  • 63
1

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)

user666
  • 1,750
  • 3
  • 18
  • 34
1

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();
        }
    }

}
0

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
    }
}
desertnaut
  • 57,590
  • 26
  • 140
  • 166
nAtxO Pi
  • 21
  • 6