javaspringspring-bootqualifiers

SpringBoot: Configurable @Qualifier to select bean


Small question for SpringBoot, and how to configure the bean using @Qualifier please.

I have a very straightforward piece of code:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.0.1</version>
        <relativePath/>
    </parent>

    <groupId>com.question</groupId>
    <artifactId>language</artifactId>
    <version>1.1</version>

    <name>language</name>
    <description>Spring Boot</description>

    <properties>
        <java.version>17</java.version>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

package com.question;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class LanguageApplication {

    public static void main(String[] args) {
        SpringApplication.run(LanguageApplication.class, args);
    }

}
package com.question.service;

public interface LanguageService {

    String process(String name);

}

package com.question.service;

import org.springframework.stereotype.Service;

@Service("french")
public class FrenchLanguageServiceImpl implements LanguageService {

    @Override
    public String process(String name) {
        return "Bonjour " + name;
    }

}

package com.question.service;

import org.springframework.stereotype.Service;

@Service("english")
public class EnglishLanguageServiceImpl implements LanguageService {

    @Override
    public String process(String name) {
        return "Welcome " + name;
    }

}

package com.question.controller;

import com.question.service.LanguageService;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
public class LanguageController {

    private final LanguageService languageService;

    @Value("${configuration}")
    public String configuration;

    public LanguageController(@Qualifier(configuration) LanguageService languageService) {
        this.languageService = languageService;
    }

    @GetMapping("/test")
    public String test(@RequestParam String name) {
        return languageService.process(name);
    }

}

Expected:

What I hope to achieve is equally straightforward. I would like to pass some sort of configuration to application.properties, something like configuration=french or configuration=english.

At the controller layer, to use (@Qualifier(configuration) LanguageService languageService) And the correct concrete service will be used.

Actual:

Unfortunately,

@Qualifier(configuration) + @Value("${configuration}") public String configuration;

will yield Attribute Value must be constant.

Is there a way we can configure the concrete Bean via a configurable @Qualifier please?

I understand there is a way to workaround this by using ApplicationContext getBean.

But having this construct: @Qualifier(configuration) makes the code clean and easily understandable. How to achieve this please?

Thank you


Solution

  • If you only need 1 of the LanguageService beans active at a time, then you can use @ConditionalOnProperty on each of them, each using a unique havingValue. Like this (warning, untested code written from memory):

    interface ConfigKeys {
        public static final String LANGUAGE = "my.app.prefix.language";
    }
    
    
    @Service
    @ConditionalOnProperty(name = ConfigKeys.LANGUAGE, havingValue = "english")
    public class EnglishLanguageServiceImpl implements LanguageService {
    
        @Override
        public String process(String name) {
            return "Welcome " + name;
        }
    
    }
    
    @Service
    @ConditionalOnProperty(name = ConfigKeys.LANGUAGE, havingValue = "french")
    public class FrenchLanguageServiceImpl implements LanguageService {
    
        @Override
        public String process(String name) {
            return "Bonjour " + name;
        }
    
    }
    

    With that, you don't need any qualifiers, just set the my.app.prefix.language property in your config (application.properties or application.yaml) to the value you want, and there will just one LanguageService bean in the context. You can inject that bean wherever you need it without needing a qualifier.