Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 801
user666 Avatar asked Aug 06 '19 12:08

user666


People also ask

Can we use @qualifier and @bean together?

NOTE: if you are creating bean with @Bean, it will be injected byType if there is duplicates then it will injected byName. we no need to mention @Bean(name="bmwDriver") . so you can directly use qualifier("bmwDriver") wherever you need in classes.

Can we use @qualifier and @primary together?

We can use @Qualifier and @Primary for the same bean. Use @Qualifier to inject specific bean otherwise Spring injects bean by default which is annotated with @Primary.

What does @qualifier do in Spring boot?

By using the @Qualifier annotation, we can eliminate the issue of which bean needs to be injected. By including the @Qualifier annotation, together with the name of the specific implementation we want to use, in this example Foo, we can avoid ambiguity when Spring finds multiple beans of the same type.

What is @configuration in Spring boot?

Spring @Configuration annotation is part of the spring core framework. Spring Configuration annotation indicates that the class has @Bean definition methods. So Spring container can process the class and generate Spring Beans to be used in the application.


3 Answers

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.

like image 110
Mark Bramnik Avatar answered Oct 18 '22 01:10

Mark Bramnik


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.

like image 4
Maciej Kowalski Avatar answered Oct 18 '22 01:10

Maciej Kowalski


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)

like image 1
user666 Avatar answered Oct 18 '22 01:10

user666