Spring 如何使用运行时动态注入服务;限定词;变量

Spring 如何使用运行时动态注入服务;限定词;变量,spring,service,dependency-injection,autowired,Spring,Service,Dependency Injection,Autowired,在给定运行时值的情况下,我找不到注入组件/服务的简单方法 我开始阅读@Spring的文档: 但是我找不到如何对传递给@Qualifier注释的值进行变量化 假设我有一个具有这样接口的模型实体: public interface Case { String getCountryCode(); void setCountryCode(String countryCode); } 在我的客户机代码中,我将执行以下操作: @Inject DoService does; (...)

在给定运行时值的情况下,我找不到注入组件/服务的简单方法

我开始阅读@Spring的文档: 但是我找不到如何对传递给@Qualifier注释的值进行变量化

假设我有一个具有这样接口的模型实体:

public interface Case {

    String getCountryCode();
    void setCountryCode(String countryCode);

}
在我的客户机代码中,我将执行以下操作:

@Inject
DoService does;

(...)

Case myCase = new CaseImpl(); // ...or whatever
myCase.setCountryCode("uk");

does.whateverWith(myCase);
。。。我的服务是:

@Service
public class DoService {

    @Inject
    // FIXME what kind of #$@& symbol can I use here?
    // Seems like SpEL is sadly invalid here :(
    @Qualifier("${caze.countryCode}")
    private CaseService caseService;

    public void whateverWith(Case caze) {
        caseService.modify(caze);
    }

}
我希望caseService是UKCaseService(参见下面的相关代码)

那么,我如何通过使用/all-Spring特性(本质上没有.properties,没有XML,只有注释)以最简单/优雅/高效的方式“修复”所有这些呢。 然而,我已经怀疑我的DoService有问题,因为Spring在注入caseService之前需要知道“案例”。。。但是如何在客户代码不知道caseService的情况下实现这一点?! 我搞不懂这个

我已经在这里读了好几期了,但大多数时候,要么它们的需求和/或配置与我的不一样,要么发布的答案对我来说不够满意(看起来它们基本上是变通方法,或者(旧的)Spring特性的(旧的)用法)

=>仅指类似组件的类

=>非常有趣,但最详细的答案(4票)是。。。快三岁半了?!(2013年7月)

=>这里的问题非常相似,但答案看起来确实像是一个解决方案,而不是一个真正的设计模式(如工厂)?我不喜欢在ServiceImpl中实现所有代码,因为它已经完成了

=>第二个答案似乎很有趣,但它的作者并没有展开,所以尽管我(稍微)了解Java配置和东西,但我不确定他在说什么

=>有趣的讨论,特别是答案,但用户设置了属性,而我没有

还请阅读以下内容: =>我找不到有关表达式中使用“@”的扩展示例。有人知道这件事吗

编辑: 发现其他与类似问题相关的问题,没有人得到正确答案:

工厂模式可能是一个解决方案?

您可以使用以下方法从上下文中按名称动态获取bean:

旁注。使用类似国家代码的名称作为bean名称看起来有点奇怪。至少添加一些前缀或更好地考虑其他设计模式。

如果你仍然希望每个国家都有豆子,我建议另一种方法。引入注册表服务以按国家/地区代码获取所需服务:

@Service
public class CaseServices {

  private final Map<String, CaseService> servicesByCountryCode = new HashMap<>();

  @Autowired
  public CaseServices(List<CaseService> services){
    for (CaseService service: services){
      register(service.getCountryCode(), service);
    }
  }

  public void register(String countryCode, CaseService service) {
    this.servicesByCountryCode.put(countryCode, service);
  }

  public CaseService getCaseService(String countryCode){
    return this.servicesByCountryCode.get(countryCode);
  }
}
在这种情况下,您必须将
String getCountryCode()
方法添加到
CaseService
接口

public interface CaseService {
    void modify(Case case);
    String getCountryCode();
}
或者,您可以添加方法
CaseService.supports(Case)
来选择服务。或者,如果无法扩展接口,可以从某个初始化器或
@Configuration
类调用
CaseServices.register(String,CaseService)
方法

更新:忘了提到,Spring已经提供了一个很好的抽象,可以重用样板代码来创建
PluginRegistry

例如:

public interface CaseService extends Plugin<String>{
    void doSomething(Case case);
}

@Service
@Priority(0)
public class SwissCaseService implements CaseService {

  void doSomething(Case case){
    // Do something with the Swiss case
  }

  boolean supports(String countryCode){
    return countryCode.equals("CH");
  }
}

@Service
@Priority(Ordered.LOWEST_PRECEDENCE)
public class DefaultCaseService implements CaseService {

  void doSomething(Case case){
    // Do something with the case by-default
  }

  boolean supports(String countryCode){
    return true;
  }
}

@Service
public class CaseServices {

  private final PluginRegistry<CaseService<?>, String> registry;

  @Autowired
  public Cases(List<CaseService> services){
    this.registry = OrderAwarePluginRegistry.create(services);
  }

  public CaseService getCaseService(String countryCode){
    return registry.getPluginFor(countryCode);
  }
}
公共接口案例服务扩展插件{
无效剂量测定(例);
}
@服务
@优先级(0)
公共类SwissCaseService实现CaseService{
无效剂量测定(案例){
//对瑞士的案子做点什么
}
布尔支持(字符串国家代码){
返回countryCode.equals(“CH”);
}
}
@服务
@优先级(有序。最低优先级)
公共类DefaultCaseService实现CaseService{
无效剂量测定(案例){
//默认情况下对该案例进行处理
}
布尔支持(字符串国家代码){
返回true;
}
}
@服务
公营个案服务{

private final PluginRegistry根据这个答案,使用
@Qualifier
对您没有多大帮助:

至于另一种战略:

  • 如果您是spring boot,则可以使用或另一个
    条件

  • 查找服务,如
    @aux
    所示

  • 只需一致地命名bean,并在运行时按名称查找它们

请注意,您的用例似乎也围绕在应用程序启动时创建bean的场景展开,但所选择的bean需要在applicationContext完成注入bean后进行解析。

事实上,它不是“作为bean名称的国家代码”,它是作为限定值的国家代码。;)但最后,当我在上面提到工厂模式作为结论时,我开始开发类似的东西。我很高兴你找到了解决方案。我仍然认为注册表模式可能更适合这种情况,尽管我理解你的实际情况可能会有所不同,而不是那么简单如您所述。祝您好运+我认为第一个示例有点误导,因为
@autowiredbeanfactorybean;
如果没有进一步的代码,它将返回
null
我发现注册表解决方案非常优雅,我唯一的问题是,服务
公共案例服务(列表服务)如何
自动连线?Spring framework会扫描所有实现CaseInterface的可用bean并将其注入CaseServices吗?不必回答,我已经测试过了,它工作得很好。干得好,伙计!!!我想你必须小心你的工厂模式。你打算使用
myCase.setCountryCode(“英国”);
不是线程安全的,除非您使用
ThreadLocal
存储
CaseService
实例。您可能可以使用来在setter中设置ThreadLocal。调用
setCountryCode()时,我不会设置
CaseService
。这是对象的初始化。为了清晰起见,我进行了编辑。因此,您尝试使用@Qualifier在xml中执行#{bean.myMethod()}的等效操作,对吗?不,真的,因为您示例中的
bean
是一个Spring管理的bean(如
@Service
public class DoService {

  @Autowired CaseServices caseServices;

  public void doSomethingWith(Case case){
    CaseService service = caseServices.getCaseService(case.getCountryCode());
    service.modify(case);
  }
}
public interface CaseService {
    void modify(Case case);
    String getCountryCode();
}
public interface CaseService extends Plugin<String>{
    void doSomething(Case case);
}

@Service
@Priority(0)
public class SwissCaseService implements CaseService {

  void doSomething(Case case){
    // Do something with the Swiss case
  }

  boolean supports(String countryCode){
    return countryCode.equals("CH");
  }
}

@Service
@Priority(Ordered.LOWEST_PRECEDENCE)
public class DefaultCaseService implements CaseService {

  void doSomething(Case case){
    // Do something with the case by-default
  }

  boolean supports(String countryCode){
    return true;
  }
}

@Service
public class CaseServices {

  private final PluginRegistry<CaseService<?>, String> registry;

  @Autowired
  public Cases(List<CaseService> services){
    this.registry = OrderAwarePluginRegistry.create(services);
  }

  public CaseService getCaseService(String countryCode){
    return registry.getPluginFor(countryCode);
  }
}