Внедрение контекстов и зависимостей (CDI), определяемое документом JSR-299, является неотъемлемой частью Java EE 6 и обеспечивает архитектуру, позволяющую компонентам Java EE (например, сервлетам, компонентам EJB и JavaBeans) существовать в жизненном цикле приложения с четко определенными областями действия. Кроме того, службы CDI позволяют компонентам Java EE (например, сеансным компонентам EJB и управляемым компонентам JavaServer Faces) внедряться и взаимодействовать слабо связанным образом путем запуска и обработки событий.
Этот учебный курс основан на записи блога Энди Гибсона (Andy Gibson) под заголовком Введение в CDI, часть 2: Внедрение В нем рассматривается использование аннотации @Alternative для настройки приложения для различных развертываний, а также показано, как использовать аннотации жизненного цикла управляемых компонентов, например, @PostConstruct и @PreDestroy для совмещения внедрения CDI с функциональными возможностями спецификации управляемых компонентов Java EE 6
Среда IDE NetBeans обеспечивает встроенную поддержку внедрения контекстов и зависимостей, в том числе параметра создания файла настройки CDI beans.xml для создания проектов, поддержку изменения и переходов для аннотаций, а также различных мастеров создания наиболее распространенных артефактов CDI.
Для работы с этим учебным курсом требуется программное обеспечение и материалы, перечисленные ниже.
В состав пакета Java среды IDE NetBeans также входит сервер GlassFish Open Source Edition 3.x, являющийся контейнером, совместимым с Java EE 6.
Поддержка CDI также доступна в NetBeans 6.8 после установки Исправления 1.
Пример проекта к этому учебному курсу можно загрузить здесь: cdiDemo3.zip
Обработка нескольких развертываний
CDI предлагает использовать аннотацию @Alternative, которая позволяет пакертировать несколько компонентов, соответствующих одной точке внедрения, без неоднозначностей. Другими словами, аннотация @Alternative может применяться к двум или нескольким компонентам, а затем, в зависимости от развертывания, в файле настройки CDI beans.xml указывается компонент для использования.
Для наглядности рассмотрим следующий случай. ItemValidator добавляется в главный класс ItemProcessor. ItemValidator имеет две реализации: DefaultItemValidator и RelaxedItemValidator. В соответствие с требованиями к развертыванию в большинстве случаев требуется использовать DefaultItemValidator, а RelaxedItemValidator — в одном конкретном развертывании. Для этого создаются аннотации к каждому компоненту, а затем указывается компонент, который используется для данного развертывания путем добавления записей в файл приложения beans.xml.
Начните работу с извлечения начального проекта из файла cdiDemo2.zip (см. выше таблицу необходимых ресурсов). Откройте проект в среде IDE, выбрав "Файл" > "Открыть проект" (Ctrl-Shift-O; ⌘-Shift-O на Mac), затем выбрав проект из его местоположения на компьютере.
Создайте интерфейс ItemValidator.
Щелкните "Создать файл" или нажмите Ctrl-N (⌘-N на Mac), чтобы открыть мастер создания файла.
Выберите категорию Java, а затем команду "Интерфейс Java". Нажмите кнопку "Далее".
Введите имя класса в ItemValidator и пакет exercise3.
Нажмите кнопку "Готово". Интерфейс будет создан и открыт в редакторе.
Добавьте метод isValid(), который принимает в качестве параметра объект Item и возвращает логическое значение boolean.
public interface ItemValidator {
boolean isValid(Item item);
}
С помощью подсказки редактора добавьте оператор импорта для exercise2.Item.
Расширьте класс ItemProcessor для добавления новой возможности. Откройте в редакторе ItemProcessor и внесите следующие изменения.
С помощью подсказки редактора добавьте оператор импорта для exercise3.ItemValidator.
Создайте реализацию ItemValidator под названием DefaultItemValidator, которая просто сравнивает значение с предельным.
В окне "Проекты" щелкните правой кнопкой пакет exercise3 и выберите команду "Создать" > "Класс Java". Дайте классу имя DefaultItemValidator и нажмите кнопку "Готово".
В элементе DefaultItemValidator реализуйте ItemValidator и переопределите метод isValid() следующим образом.
public class DefaultItemValidator implements ItemValidator {
@Override
public boolean isValid(Item item) {
return item.getValue() < item.getLimit();
}
}
С помощью подсказки редактора добавьте оператор импорта для exercise2.Item.
Нажмите кнопку "Выполнить проект" () на главной панели инструментов среды IDE. Файл скомпилирован и развернут в GlassFish, и страница приветствия приложения (process.xhtml) отображается в веб-браузере.
Нажмите кнопку Выполнить на странице. Вернитесь в среду IDE и проверьте протокол сервера GlassFish. Протокол сервера отображается в окне вывода (Ctrl-4; ⌘-4 на Mac) на вкладке GlassFish. В нем видно, что элементы проверяются и перечисляются только допустимые элементы, значение которых меньше предельного.
Теперь рассмотрим случай, в котором вам необходимо выполнить развертывание в другом месте, менее жестком, считающим компонент недопустимым только в том случае, если его значение более чем в два раза превышает ограничение. Может потребоваться другой компонент для реализации интерфейса ItemValidator для данной логики.
Создайте новую реализацию ItemValidator с именем RelaxedItemValidator. В окне "Проекты" щелкните правой кнопкой пакет exercise3 и выберите команду "Создать" > "Класс Java". Дайте классу имя RelaxedItemValidator и нажмите кнопку "Готово".
Сделайте RelaxedItemValidator реализацией ItemValidator и переопределите метод isValid() следующим образом.
public class RelaxedItemValidator implements ItemValidator {
@Override
public boolean isValid(Item item) {
return item.getValue() < (item.getLimit() * 2);
}
}
С помощью подсказки редактора добавьте оператор импорта для exercise2.Item.
Нажмите кнопку "Выполнить проект" () для выполнения проекта. Обратите внимание, что теперь развертывание проекта завершается сбоем.
Просмотрите протокол сервера в окне вывода (Ctrl-4; ⌘-4 на Mac). В протоколе отображается сообщение об ошибке неоднозначной зависимости. Это происходит по причине того, что имеются два кдласса, реализующих один и тот же интерфейс.
org.glassfish.deployment.common.DeploymentException: точка внедрения имеет неоднозначные зависимости.
Точка внедрения: field exercise2.ItemProcessor.itemValidator;
Квалификаторы: [@javax.enterprise.inject.Default()];
Возможные зависимости: [exercise3.RelaxedItemValidator, exercise3.DefaultItemValidator]
Реализация Weld CDI не способна определить элемент, используемый для данной точки внедрения (RelaxedItemValidator или DefaultItemValidator).
Как указано выше, единственное отличие связано с развертыванием. Для большинства развертываний можно использовать средство проверки по умолчанию, однако для одного развертывания может потребоваться использование "нежесткой" реализации. В CDI существует аннотация @Alternative, которая позволяет пакетировать несколько компонентов, соответствующих одной точке внедрения, без проблем неоднозначности, поскольку при этом используется компонент, указанный в файле beans.xml. Это позволяет развертывать в одном модуле обе реализации. При этом отличается только определение в файле beans.xml, которое уникально для каждой реализации.
Добавьте аннотацию @Alternative и соответствующий оператор импорта в RelaxedItemValidator и DefaultItemValidator.
Откройте в редакторе RelaxedItemValidator и внесите следующие изменения.
import javax.enterprise.inject.Alternative;
...
@Alternative
public class RelaxedItemValidator implements ItemValidator {
public boolean isValid(Item item) {
return item.getValue() < (item.getLimit() * 2);
}
}
Введите '@Al', затем нажмите CTRL+ПРОБЕЛ для вызова автозавершения кода. Поскольку возможен только один вариант, аннотация @Alternative завершается, а в начале файла автоматически добавляется соответствующий оператор импорта для javax.enterprise.inject.Alternative. Как правило, при нажатии CTRL+ПРОБЕЛ в аннотациях также вызывается всплывающая документация Javadoc.
Переключитесь к DefaultItemValidator (нажмите сочетание клавиш CTRL+TAB) и внесите следующее изменение.
import javax.enterprise.inject.Alternative;
...
@Alternative
public class DefaultItemValidator implements ItemValidator {
public boolean isValid(Item item) {
return item.getValue() < item.getLimit();
}
}
Выполняя развертывание приложения сейчас, вы получите ошибку "неудовлетворенная зависимость", так как два подходящих компонента были определены как альтернативные, но ни один из них не был активирован в файле beans.xml.
С помощью диалогового окна "Переход к файлу" в среде IDE откройте файл beans.xml. Выберите пункт "Переход" > "Переход к файлу" в главном меню среды IDE (сочетание клавиш ALT+SHIFT+O; CTRL+SHIFT+O в Mac OS), затем введите beans. Нажмите кнопку "ОК".
Данная команда указывает CDI использовать RelaxedItemValidator для данного развертывания. Аннотация @Alternative может рассматриваться как способ отключения компонента, запрещения его внедрения и разрешения пакетирования реализации с другими компонентами. Добавление компонента в качестве альтернативы в файл beans.xml фактически снова разрешает компонент, делая его доступным для внедрения. Перемещение этого вида метаданных в файл beans.xml позволяет связать различные версии файла с различными развертываниями.
Нажмите кнопку "Выполнить проект" () для запуска проекта (или нажмите клавишу F6; сочетание клавиш fn+F6 в Mac OS). В обозревателе нажмите кнопку 'Выполнить' на отображаемой странице. Переключитесь обратно на среду IDE и просмотрите протокол сервера GlassFish, отображающийся в окне вывода (Ctrl-4; ⌘-4 на Mac).
Используется реализация RelaxedItemValidator, а третий элемент отображается как верный, хотя значение (24) больше заданного предела (19).
Применение аннотаций жизненного цикла к управляемым компонентам
В этом упражнении ItemErrorHandler будет добавлен в главный класс ItemProcessor. Для добавления выбран FileErrorReporter, поскольку он является единственной реализацией интерфейса ItemErrorHandler. Для настройки зависящих от жизненного цикла действий класса следует использовать аннотации @PostConstruct и @PreDestroy из спецификации управляемых компонентов (включенной в JSR 316: спецификация платформы Java, Enterprise Edition 6).
После этого необходимо создать интерфейс ItemErrorHandler для обработки обнаруженных недопустимых элементов.
В окне "Проекты" щелкните правой кнопкой пакет exercise3 и выберите команду "Создать" > "Интерфейс Java".
В мастере интерфейсов Java введите имя класса ItemErrorHandler и имя пакета exercise3. Нажмите кнопку "Готово".
Интерфейс будет создан и открыт в редакторе.
Добавьте метод handleItem(), принимающий параметр типа Item.
public interface ItemErrorHandler {
void handleItem(Item item);
}
С помощью подсказки редактора добавьте оператор импорта для exercise2.Item.
Выполните реализацию ItemErrorHandler с фиктивным обработчиком FileErrorReporter, сохраняющим данные элемента в файл.
В окне "Проекты" щелкните правой кнопкой пакет exercise3 и выберите команду "Создать" > "Класс Java". Присвойте классу имя FileErrorReporter и нажмите кнопку "Готово".
Сделайте FileErrorReporter реализацией ItemErrorHandler и переопределите метод handleItem() следующим образом.
public class FileErrorReporter implements ItemErrorHandler {
@Override
public void handleItem(Item item) {
System.out.println("Saving " + item + " to file");
}
}
(С помощью подсказки редактора добавьте оператор импорта для exercise2.Item.)
Вам нужно открыть файл до начала обработки элементов, оставить его открытым в течение процесса добавления содержимого в данный файл, а затем закрыть его по завершении процесса. Можно вручную добавить методы initProcess() и finishProcess() к компоненту средства сообщения об ошибке, но в этом случае вы не сможете выполнить кодирование интерфейса, так как вызывающей стороне будет необходимо знать данные специфичные для класса методы. Можно добавить те же методы к интерфейсу ItemErrorReporter, но в этом случае потребуется выполнить ненужное внедрение данных методов в каждый класс, реализующий данный интерфейс. Вместо этого можно использовать несколько аннотаций жизненного цикла из спецификации управляемых компонентов (входящей в JSR 316: спецификация платформы Java, Enterprise Edition 6) для вызова методов в компоненте в конкретных точках жизненного цикла компонента. Метод с аннотацией @PostConstruct вызывается после создания компонента и учета всех его зависимостей. Метод с аннотацией @PreDestroy аналогичным образом вызывается непосредственно перед удалением компонента контейнером.
Добавьте следующие методы init() и release() с аннотациями @PostConstruct и @PreDestroy.
public class FileErrorReporter implements ItemErrorHandler {
@PostConstruct
public void init() {
System.out.println("Creating file error reporter");
}
@PreDestroy
public void release() {
System.out.println("Closing file error reporter");
}
@Override
public void handleItem(Item item) {
System.out.println("Saving " + item + " to file");
}
}
Исправьте операторы импорта. Щелкните правой кнопкой мыши редактор и выберите "Исправить операторы импорта" или нажмите Ctrl-Shift-I (⌘-Shift-I на Mac). В начало файла добавляются операторы импорта для javax.annotation.PostConstruct и javax.annotation.PreDestroy.
После этого добавьте новый компонент ItemErrorHandler к ItemProcessor.
@Named
@RequestScoped
public class ItemProcessor {
@Inject @Demo
private ItemDao itemDao;
@Inject
private ItemValidator itemValidator;
@Inject
private ItemErrorHandler itemErrorHandler;
public void execute() {
List<Item> items = itemDao.fetchItems();
for (Item item : items) {
if (!itemValidator.isValid(item)) {
itemErrorHandler.handleItem(item);
}
}
}
}
С помощью подсказки редактора добавьте оператор импорта для exercise3.ItemErrorHandler.
Нажмите кнопку "Выполнить проект" () для запуска проекта (или нажмите клавишу F6; сочетание клавиш fn+F6 в Mac OS). В обозревателе нажмите кнопку 'Выполнить', отображаемую на странице. Переключитесь обратно на среду IDE и просмотрите протокол сервера GlassFish, отображающийся в окне вывода (Ctrl-4; ⌘-4 на Mac).
Различные развертывания приложений могут использовать различные правила обработки недопустимых элементов: отклонение элементов, отправку уведомлений, выделение элементов или перечисление их в выходном файле. Кроме того, может потребоваться комбинация этих действий (например, отклонить заказ, отправить письмо менеджеру и записать заказ в файл). Оптимальным способом обработки такой многогранной проблемы является использование событий. События CDI рассматриваются в последнем примере этой серии.