Spring Boot - 在@Qualifier中使用可配置的值

5

我有一个接口的两个实现,我想根据配置选择使用哪个实现。限定符解决方案由于在配置之前初始化而无法使用。我该怎么做?


没有限定符,它能正常工作吗? - Jose Martinez
请问您能描述一下您的使用情况吗?当应用程序上下文中存在多个相同类型的bean并且您想要注入特定的bean时,可以使用限定符。在这种情况下通常不需要动态配置,您可以从预定义的值集合中选择以限定符方式注入bean。 - Mark Bramnik
@JoseMartinez 如果没有它,我将不得不手动添加构造函数依赖项,在Spring项目中并不是很好。 - user666
@MarkBramnik 我有两种不同的工作实现方式,每个月都可以更改类型,因此如果可配置,则可以减少部署和代码更改。 - user666
可能是如何在Spring Boot中从属性文件读取Qualifier?的重复问题。 - Andy Brown
5个回答

14

我已经收到您的评论:

我有两种不同的任务实现方式,每个月可以更改类型,因此如果可配置,则可以减少部署和代码更改。

您可能会像这样:

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

如果我理解正确的话,同时在同一应用程序上下文中加载两个bean是没有意义的。

但如果是这样的话,那么@Qualifier不是合适的工具。

我建议使用集成到Spring Boot中的条件来解决:

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

现在,在application.properties(或yaml,无论你有哪种)中定义:

 job.name = jobA # or jobB

当然,您可以使用更符合您业务领域的、更容易理解的名称,而不是jobA/jobB


4

如果您在Spring基于Java的配置上稍微调整一下,就可以做到这一点,其中您可以根据配置值以编程方式决定正确的实现:

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

在限定符上,您需要放置:

@Qualifier("myBeanByConfig")

您可能需要在配置类上添加@ComponentScan@PropertySource


1
假设你有一个接口:

public interface Foo {
    void doSomething();
}

还有两种实现方式:

public class Foo_A implements Foo {
    @Override
    doSomething() {...}
}

public class Foo_B implements Foo {
    @Override
    doSomething() {...}
}

现在你想根据属性文件中的属性值使用Foo_A/Foo_B:

foo_name: "A"

我发现最简单的方法是:

  1. 首先,你需要对你的实现进行资格认证

@Component("Foo_A")
public class Foo_A implements Foo {
    @Override
    doSomething() {...}
}

@Component("Foo_B")
public class Foo_B implements Foo {
    @Override
    doSomething() {...}
}


然后,无论您在哪里使用它(例如类Bar),您只需使用@Qualifier指定要实例化的实现,并从带有@Value的属性中获取值。然后,在方法内部,使用简单的if / else语句,使用属性值来决定要调用哪个实现。
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();
        }
    }

}

1

我最终将两个自动装配的实现添加到主应用程序类中,然后为每个定义一个bean:

@Autowired
TypeOneImpl typeOneImpl
@Bean(name = "typeOneImpl")
public InterfaceRClass getTypeOneImpl()
{
    return typeOneImpl;
}

然后在另一个类中我定义了一个配置字段。
@Value("${myClass.type}")
private String configClassType;
// the below should be defined in constructor
private final InterfaceRClass interfaceRClassElement ;

并使用@Autowired注解为其添加了一个setter方法:

@Autowired
public void setMyClassType(ApplicationContext context) {
    interfaceRClassElement = (InterfaceRClass) context.getBean(configClassType);
}

在配置中,该值应该是typeOneImpl(typeTwoImpl是用于附加实现的)


0

知道通过构造函数IOC工作正常,也许我会这样做:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接