Как динамически вводить службу с использованием переменной "квалификатор" времени выполнения?
Я не могу найти простой способ внедрить компонент/сервис с учетом значения времени выполнения.
Я начал читать @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.