Как динамически вводить службу с использованием переменной "квалификатор" времени выполнения?

Я не могу найти простой способ внедрить компонент/сервис с учетом значения времени выполнения.

Я начал читать @Spring doc: 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 #[email protected]& 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, так что, по сути, NO.properties, NO XML, только аннотации. Тем не менее, я уже подозреваю, что что-то не так в моем DoService, потому что Spring должен был бы знать "case", прежде чем вводить caseService... но как этого добиться, если клиентский код не знает о caseService?! Я не могу понять это...

Я уже читал несколько вопросов здесь о SO, но в большинстве случаев они либо не имеют таких же потребностей и/или конфигурации, что и я, либо опубликованные ответы не удовлетворяют меня (похоже, они по сути обходные пути или (старое) использование (старых) функций Spring).

Как Spring автоматически связывается по имени, когда найдено более одного совпадающего бина? => относится только к компонентоподобным классам

Динамическое определение того, какой bean-компонент должен автоматически подключаться в Spring (с использованием квалификаторов) => Действительно интересный, но самый сложный ответ (4 голоса)... почти 3,5 года?! (Июль 2013 г.)

Spring 3 - Динамическое автоматическое подключение во время выполнения на основе другого атрибута объекта => Довольно похожая проблема здесь, но ответ действительно выглядит как обходной путь, а не как реальный шаблон проектирования (например, фабрика)? и мне не нравится внедрять весь код в ServiceImpl, как это было...

Spring @Autowiring, как использовать фабрику объектов для выбора реализации? => 2-й ответ кажется интересным, но его автор не раскрывает, так что я немного знаю о Java Config & хм, я не совсем уверен, о чем он говорит...

Как внедрить различные сервисы во время выполнения на основе свойства Spring без XML => интересная дискуссия, особенно ответ, но у пользователя есть набор свойств, которых у меня нет.

Также прочитайте это: http://docs.spring.io/spring/docs/current/spring-framework-reference/html/expressions.html#expressions-bean-references => Я не могу найти расширенные примеры использования "@" в выражениях. Кто-нибудь знает об этом?

Редактировать: Нашли другие похожие на похожие вопросы, никто не получил правильный ответ: Как использовать @Autowired для динамического внедрения реализации как фабричного шаблона Spring Qualifier и заполнитель свойств Spring: использование @Qualifier с заполнителем свойства Как сделать условную автопроводку spring? Динамический впрыск spring SpEL в @Qualifier относится к одному и тому же компоненту Как использовать SpEL для вставки результата вызова метода в Spring?

Заводская модель может быть решением? Как использовать @Autowired для динамического внедрения реализации как фабричного шаблона

Ответ 1

Вы можете получить свой компонент из контекста по имени динамически, используя BeanFactory:

@Service
public class Doer {

  @Autowired BeanFactory beans;

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

Примечание. Использование кода страны в качестве имени бина выглядит немного странно. Добавьте хотя бы какой-нибудь префикс или лучше рассмотрите другой шаблон дизайна.

Если вы по-прежнему хотели бы иметь бин в каждой стране, я бы предложил другой подход. Введите службу реестра, чтобы получить необходимую службу по коду страны:

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

Пример использования:

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

ОБНОВЛЕНИЕ: Забыл упомянуть, что в 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);
  }
}

Ответ 2

В соответствии с этим ответом SO использование @Qualifier не поможет вам: Получите bean из ApplicationContext по квалификатору

Что касается альтернативной стратегии:

  • Если вы загрузили spring, вы можете использовать @ConditonalOnProperty или другой Conditional.

  • служба поиска, поскольку @aux предлагает

  • просто укажите свой beans и посмотрите их по имени во время выполнения.

Обратите внимание, что ваш прецедент также вращается вокруг сценария, в котором beans создаются при запуске приложения, но выбранный bean должен быть разрешен после того, как applicationContext завершил инъекцию beans.