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
CodePudding user response:
You can't make this dynamic, you'll need to use a workaround. A factory pattern would do well, here. Instead of one bean, you would have two, and based on the value of that property, you would let your factory choose which one to use.
@Component
public class LanguageFactory {
@Autowired
@Qualifier("english")
private LanguageService englishLanguage;
@Autowired
@Qualifier("french")
private LanguageService frenchLanguage;
@Value("${configuration}")
private String chosenLanguage;
public LanguageService getLanguageService() {
switch(chosenLanguage) {
case "english": return englishLanguage;
case "french": return frenchLanguage;
// add any other language you need
default: throw new Exception("Invalid language: " chosenLanguage);
}
}
}
So, in your LanguageController, you would get something like this:
@Autowired
private LanguageFactory languageFactory;
@GetMapping("/test")
public String test(@RequestParam String name) {
return languageFactory.getLangaugeService().process(name);
}
EDIT:
Another option would be to use a Map. You would avoid all the if-elses, but you would still have to add them to a Map.
@Component
public class LanguageFactory {
private Map<String, LanguageService> languages = new HashMap<>();
@Value("${configuration}")
private String chosenLanguage;
@Autowired
public LanguageFactory(@Qualifier("english") LanguageService englishLanguage, @Qualifer("french") LanguageService frenchService) {
this.languages.put("english", englishLanguage);
this.languages.put("french", frenchLanguage);
}
public LanguageService getLanguageService() {
LanguageService ls = this.languages.get(chosenLanguage);
if ( ls == null ) {
// handle the issue as you see fit
}
return ls;
}
}
CodePudding user response:
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(ConfigKeys.LANGUAGE, havingValue = "english")
public class EnglishLanguageServiceImpl implements LanguageService {
@Override
public String process(String name) {
return "Welcome " name;
}
}
@Service
@ConditionalOnProperty(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.
CodePudding user response:
A much simpler option would be to group the related beans in @Configuration
classes, and then use @Conditional
to enable (or not) the configuration, and in turn, a set of beans. That way, you’re not dealing with individual beans, as you say you’ve many.