Заменить фабричное создание объектов механизмом CDI
Я хотел ввести CDI (Weld) в наш проект и теперь испытываю некоторые проблемы с объектами, которые создаются вручную.
Таким образом, у нас есть несколько классов, реализующих интерфейсIReport
, которые имеют поле, которое должно быть введено. Это значение равно null во время выполнения, поскольку все эти классы генерируются ReportFactory
в классе ReportController
.
private Map<String,Object> generateReport(ReportInfo ri, ...) {
// some input validation
IReport report = ReportControllerFactory.getReportInstance( ri.getClassName() );
// ...
}
Я знаю, что могу использовать аннотацию @Produces
вместе с другой пользовательской аннотацией в ReportControllerFactory
, но как я использую @Inject
для переменной, которая может быть создана только после некоторой проверки, внутри метода? И как бы я представил параметр ri.getClassName()
? Объект ri
неизвестен при построении объекта ReportController
.
Большое вам спасибо!
С уважением, Себастьян
Правка в июле 08, 2011 (10:00):
Класс ReportFactory:
public static IReport getReportInstance( String className ) throws ReportException {
IReport report = null;
try {
Class<?> clazz = Class.forName( className );
report = (IReport) clazz.newInstance();
}
catch ( Exception e ) { … }
return report;
}
Правка 2 (Выбор правильной реализации отчета)
Экземпляр отчета выбирается некоторыми пути, которые идут от интерфейса JSF к контроллеру ReportController. ManagedBean вызывает Боб сеанса, который имеет несколько методов, в зависимости от того, какая кнопка была нажата где. Все эти методы задают имя отчета и вызывают более общий метод sendOrGetReport
. Этот метод выбирает уникальный ключ для указанного отчета из базы данных и решает, следует ли отправить электронное письмо или немедленно доставить отчет. Давайте предположим, что он должен быть доставлен.
Затем приходит ReportController
в игру. Он выбирает объект ReportInfo
на основе уникального ключа и другой информации, предоставленной описанными выше методами, и вызывает объект ReportFactory
для создания отчета типа ri.getClassName()
.
Прикольно, а? Я думаю, что весь этот раздел может нуждаться в некотором рефакторинге. Если вы не видите никакого легкого места, я пропускаю @Inject
в реализации отчета и делаю поиск JDNI для этого ресурса.
3 ответа:
Идея CDI (и других DI-фреймворков) для управления зависимостями состоит в том, чтобы взять на себя управление жизненным циклом управляемых бобов. Это подразумевает, среди прочего, что вы не можете вмешиваться в создание управляемых бобов, если хотите, чтобы ими управлял контейнер.
Конечно, это не означает, что ваш сценарий неразрешим, вам просто нужно немного изменить свою точку зрения; -)Идея состоит в том, чтобы работать с управляемыми бобами (очевидно), но пусть ваша собственная логика решите, какой экземпляр из всех доступных экземпляров является правильным.
... @Inject Instance<IReport> availableReports; ... @Produces public IReport createReport() { IReport result; for (IReport report: availableReports) { // choose correct instance, you might want to query the injection // point or any attached qualifier a bit more in order to // determine which is the correct instance if ... result = report; ... } return result; } ...
С таким количеством бобов beantype IReport, как вам нравится
public class AReport implements IReport { ... @Inject ... } public class BReport implements IReport { ... @Inject ... }
И автомагическое использование, подобное этому
public class MyStuff { ... @Inject IReport myReport; ... }
Смотритездесь издесь для получения дополнительной информации.
Если я правильно понял вашу проблему, это должно продвинуть вас вперед - не стесняйтесь оставлять дальнейшие вопросы / комментарии.
Обновление :
Все может быть просто до смерти просто, если что-то вроде это соответствует вашим требованиям:
@AReport public class AReport implements IReport { ... @Inject ... } @BReport public class BReport implements IReport { ... @Inject ... }
С использованием, как это
public class MyStuff { ... @Inject @AReport IReport myAReport; ... @Inject @BReport IReport myBReport; ... }
Для динамического создания правильного класса отчета небольшое изменение принятого ответа решит проблему. Решение заключается в том, что экземпляр<...> дает вам список всех бобов, которые относятся к определенному типу. А производить аннотацию не нужно.
Создайте фабричный класс, который может выбирать между введенными экземплярами во время выполнения
public class ReportFactory { @Inject Instance<IReport> availableReports; public IReport createReport(String type) { for (IReport report: availableReports) { if (report.getType().equals(type)) { //or whatever test you need return report; } } return null; }
Теперь класс, которому нужен динамически выбранный отчет, может использовать эту фабрику.
public class ReportCreator { @Inject private ReportFactory reportFactory; public void createReport(String type) { IReport report = reportFactory.createReport(type); report.execute(); } }
Итак, если я не слишком ошибаюсь, основываясь на doc ( это правильная структура ? http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-beanmanagerprovider.html ), Ваша фабрика должна была бы получить дескриптор для синглтона BeanManager (либо ввести его, либо вызвать какой-то метод доступа из фреймворка) и сделать что-то в соответствии с
Class<?> clazz = Class.forName( className ); report = beanManager.getBean(clazz);
Предполагая, что ваш CDI был настроен для обработки каждого из возможных имен классов, вы должны получить правильный Боб. Теперь это может быть всегда один и тот же случай ; я не знаю, если это то, что вам нужно, извините.
Извините, если я ошибаюсь ; надеюсь, это поможет.