【问题标题】:SpringBoot: Configurable @Qualifier to select beanSpringBoot:可配置的@Qualifier来选择bean
【发布时间】:2023-02-15 08:42:49
【问题描述】:

Spring Boot 的小问题,以及如何使用 @Qualifier 配置 bean。

我有一段非常简单的代码:

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

}

预期的:

我希望实现的目标同样简单明了。我想将某种配置传递给application.properties,比如configuration=frenchconfiguration=english

在控制器层,使用(@Qualifier(configuration) LanguageService languageService) 并且将使用正确的具体服务。

实际的:

很遗憾,

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

将产生Attribute Value must be constant

请问有没有办法通过可配置的@Qualifier配置具体的Bean?

我知道有一种方法可以通过使用 ApplicationContext getBean 来解决这个问题。

但是有这样的构造:@Qualifier(configuration) 使代码干净且易于理解。请问如何做到这一点?

谢谢

【问题讨论】:

  • 您提供的@Stultuske 链接几乎是我的问题。正如您在教程示例中看到的那样@Qualifier("fooFormatter") 该值是硬编码的,不可配置的。请问如何配置好?
  • 不是。你没有。您提供了两个自动装配的 bean,并基于(如果值等于英语 -> 使用 englishqualifiedBean 否则使用 Otherbean。基本上:工厂模式
  • 这又是我的问题的重点,非常感谢。我有大约 100 多个具体的语言 bean。并且自动装配数百个具体的 bean,+ bean 名称上的巨大 if 语句不是最佳的。有没有一种方法可以使用可配置的@Qualifier 简单地配置在应用程序启动时使用哪个bean?
  • 这可能与您问题的“要点”相悖,但至少它会起作用。您不能动态构建传递给注释的值。您可以将它们全部放在一个地图中,并将特定的(例如“英语”和“法语”)映射到它们,您可以通过这种方式获取它们。

标签: java spring spring-boot qualifiers


【解决方案1】:

如果您一次只需要激活 LanguageService bean 中的一个,那么您可以对每个 bean 使用 @ConditionalOnProperty,每个使用唯一的 havingValue。像这样(警告,从内存中编写的未经测试的代码):

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

}

这样,您不需要任何限定符,只需将配置(application.properties 或 application.yaml)中的 my.app.prefix.language 属性设置为您想要的值,上下文中将只有一个 LanguageService bean。您可以在任何需要的地方注入该 bean,而无需限定符。

【讨论】:

  • 这正是我要找的。在这个过程中学到了一些新东西。 (这是name=ConfigKeys.LANGUAGE顺便说一句。接受并投票
  • 修复了答案中的错字。
【解决方案2】:

您不能使其动态化,您需要使用变通方法。 在这里,工厂模式会做得很好。您将拥有两个 bean,而不是一个 bean,并且根据该属性的值,您可以让您的工厂选择使用哪一个。

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

所以,在你的 LanguageController 中,你会得到这样的东西:

  @Autowired
  private LanguageFactory languageFactory;

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

编辑:

另一种选择是使用地图。你会避免所有的 if-else,但你仍然必须将它们添加到 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;
  }
}

【讨论】:

  • 如果我们走那条路,一个简单的applicationContext.getBean(configuration, LanguageService.class).process(name); 会更好。但这需要拉取应用程序上下文。
【解决方案3】:

一个更简单的选择是将相关的 bean 分组在 @Configuration 类中,然后使用 @Conditional 启用(或不启用)配置,然后一组 bean。这样,您就不会像您所说的那样处理单个 bean。

【讨论】:

  • 这是对的。投票。我接受了另一个答案,因为示例代码也在那里,但这绝对是正确的
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-24
  • 2021-02-25
相关资源
最近更新 更多