如何使用运行时“限定符”变量动态注入服务?

时间:2017-03-20 16:44:22

标签: spring service dependency-injection autowired

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

我开始阅读@ Spring的文档:http://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-autowired-annotation-qualifiers 但我找不到如何变量传递给@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(参见下面的相关代码)。

public interface CaseService {

    void modify(Case caze);

}

@Service
@Qualifier("uk")
public class UKCaseService implements CaseService {

}

@Service
@Qualifier("us")
public class USCaseService implements CaseService {

}

那么如何通过使用/所有Spring功能以最简单/优雅/高效的方式“修复”所有这些,所以基本上没有.properties,没有XML,只有注释。 但是我已经怀疑我的DoService出了什么问题,因为在注入caseService之前Spring需要知道“case”...但是如果没有客户端代码知道caseService,如何实现这个呢? 我无法弄明白......

我已经在SO上阅读了几个问题,但是大部分时间他们要么没有和我一样的需求和/或配置,或者发布的答案对我来说还不够令人满意(看起来像他们基本上是(旧的)Spring功能的解决方法或(旧的)用法。

How does Spring autowire by name when more than one matching bean is found? =>仅指类似组件的类

Dynamically defining which bean to autowire in Spring (using qualifiers) =>真的很有趣,但最精心的答案(4票)是......差不多3年半?? (2013年7月)

Spring 3 - Dynamic Autowiring at runtime based on another object attribute =>这里有相当类似的问题,但答案看起来像一个变通方法而不是真正的设计模式(如工厂)?而且我不喜欢将所有代码实现到ServiceImpl中,因为它已经完成......

Spring @Autowiring, how to use an object factory to choose implementation? =>第二个答案似乎很有趣,但它的作者没有扩展,所以尽管我知道(有点)Java Config&事情,我不确定他在说什么......

How to inject different services at runtime based on a property with Spring without XML =>有趣的讨论,尤其是答案,但用户已设置属性,我没有。

另请阅读: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html#expressions-bean-references =>我找不到关于在表达式中使用“@”的扩展示例。有人知道吗?

编辑: 发现其他相关的相似问题,没有人得到一个正确的答案: How to use @Autowired to dynamically inject implementation like a factory pattern Spring Qualifier and property placeholder Spring: Using @Qualifier with Property Placeholder How to do conditional auto-wiring in Spring? Dynamic injection in Spring SpEL in @Qualifier refer to same bean How to use SpEL to inject result of method call in Spring?

工厂模式可能是一个解决方案? How to use @Autowired to dynamically inject implementation like a factory pattern

2 个答案:

答案 0 :(得分:15)

您可以使用BeanFactory动态地通过名称从上下文中获取bean:

@Service
public class Doer {

  @Autowired BeanFactory beans;

  public void doSomething(Case case){
    CaseService service = beans.getBean(case.getCountryCode(), CaseService.class)
    service.doSomething(case);
  }
}

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

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

@Service
public class CaseServices {

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

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

使用示例:

@Service
public class DoService {

  @Autowired CaseServices caseServices;

  public void doSomethingWith(Case case){
    CaseService service = caseServices.getCaseService(case.getCountryCode());
    service.modify(case);
  }
}

在这种情况下,您必须将String getCountryCode()方法添加到CaseService界面。

public interface CaseService {
    void modify(Case case);
    String getCountryCode();
}

或者,您可以添加方法CaseService.supports(Case case)来选择服务。或者,如果您无法扩展界面,则可以从某个初始化程序或CaseServices.register(String, CaseService)类调用@Configuration方法。

UPDATE:忘了提一下,Spring已经提供了一个很好的Plugin抽象来重用样板代码来创建像这样的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);
  }
}

答案 1 :(得分:3)

根据这个SO答案,使用https://{account}.visualstudio.com/DefaultCollection/_apis/projects 对您没有多大帮助:Get bean from ApplicationContext by qualifier

至于另一种策略:

  • 如果您是春季启动,则可以使用@ConditonalOnProperty或其他@Qualifier

  • 查找服务,Conditional建议

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

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