Заменить фабричное создание объектов механизмом 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 2

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 был настроен для обработки каждого из возможных имен классов, вы должны получить правильный Боб. Теперь это может быть всегда один и тот же случай ; я не знаю, если это то, что вам нужно, извините.

Извините, если я ошибаюсь ; надеюсь, это поможет.