Работа с внедрением и квалификаторами в CDI

Предоставлено Энди Гибсоном (Andy Gibson)

Содержимое на этой странице применимо к IDE NetBeans 7.2, 7.3, 7.4 и 8.0

Внедрение контекстов и зависимостей (CDI), определяемое документом JSR-299, является неотъемлемой частью Java EE 6 и обеспечивает архитектуру, позволяющую компонентам Java EE (например, сервлетам, компонентам EJB и JavaBeans) существовать в жизненном цикле приложения с четко определенными контекстами. Кроме того, службы CDI позволяют компонентам Java EE (например, компонентам сеансов EJB и управляемым компонентам JavaServer Faces) внедряться и свободно взаимодействовать путем запуска и обработки событий.

Этот учебный курс основан на записи блога Энди Гибсона (Andy Gibson) Начало работы с CDI. Часть 2. Внедрение. Здесь рассматриваются способы использования внедрения CDI для ввода классов или интерфейсов в другие классы. Кроме того, здесь показаны способы применения квалификаторов CDI для кода, которые позволяют определить тип класса, который необходимо внедрить в указанной точке внедрения.

В NetBeans IDE обеспечена встроенная поддержка для внедрения контекстов и зависимостей, включая поддержку создания файла конфигурации CDI beans.xml при создании проекта, поддержку редактора и навигации для аннотаций, а также различных мастеров для создания часто используемых артефактов CD.


Для работы с этим учебным курсом требуется программное обеспечение и материалы, перечисленные ниже.

Программное обеспечение или материал Требуемая версия
IDE NetBeans Версия 7.2, 7.3, 7.4, 8.0, Java EE
Комплект для разработчика на языке Java (JDK) версия 7 или 8
Сервер GlassFish Open Source Edition 3.x или 4.x
cdiDemo.zip неприменимо

Примечания.

  • В комплект Java для IDE NetBeans также входит компонент GlassFish Server Open Source Edition, являющийся контейнером, совместимым с Java EE.
  • Пример решения для этого учебного курса можно загрузить здесь: cdiDemo2.zip

Внедрение: основной этап CDI

CDI является интерфейсом API для внедрения контекстов и зависимостей. В платформах приложений Seam и Spring зависимости обычно основаны на принципе присвоения имен компонентам, а также на привязке компонентов к точкам внедрения по именам. При переходе к этому учебному курсу по завершении Начало работы с внедрением контекстов и зависимостей и JSF 2.0 код пока содержит только ссылку на управляемый компонент по имени со страницы JSF, которая была создана после того, как мы определили имя компонента с помощью аннотации @Named. Основная роль аннотации @Named заключается в определении компонента для разрешения операторов языка выражений в приложении (обычно с помощью средств разрешения языка выражений JSF). Внедрение можно было бы реализовать посредством использования имен, но внедрение в CDI обычно выполняется иначе, так как CDI предлагает лучшее решение для выражения точек внедрения и компонентов, которые необходимо внедрить в эти точки.

В следующем примере создается класс ItemProcessor, который принимает список элементов из класса, реализующего интерфейс ItemDao. Аннотация @Inject CDI используется для демонстрации возможности внедрения компонента в другой класс. На диаграмме ниже показан сценарий, создаваемый в этом упражнении.

На диаграмме CDI отображаются объекты, созданные в этом упражнении

DAO - это объект доступа к данным.

  1. Сначала необходимо извлечь пример начального проекта из файла cdiDemo.zip (см. выше таблицу с перечислением требуемых ресурсов). Выберите File ("Файл") > Open Project ("Открыть проект") (Ctrl-Shift-O; ⌘-Shift-O on Mac) и выберите проект в его местоположении на компьютере.
  2. Щелкните правой кнопкой мыши узел проекта в окне "Проекты" и выберите команду "Свойства".
  3. Выберите категорию "Запуск" и убедитесь, что в списке "Сервер" выбран экземпляр GlassFish.
  4. Создайте новый класс Item и сохраните его в новом пакете с именем exercise2. Щелкните 'Создать файл' ( Кнопка 'Создать файл' ) или нажмите сочетание клавиш CTRL+N (⌘-N on Mac) для открытия мастера создания файлов.
  5. Выберите категорию Java, а затем выберите класс Java. Нажмите кнопку "Далее".
  6. Введите Item в качестве имени класса, затем укажите exercise2 в качестве пакета. Новый пакет будет создан после завершения работы мастера.
    Мастер классов Java
  7. Нажмите кнопку "Завершить". Выполняется создание нового класса и пакета, и класс Item открывается в редакторе.
  8. Создайте свойства value и limit для POJO Item и реализуйте метод toString(). Добавьте следующее содержимое к классу.
    public class Item {
    
        private int value;
        private int limit;
    
        @Override
        public String toString() {
            return super.toString() + String.format(" [Value=%d, Limit=%d]", value,limit);
        }
    }
  9. Добавьте методы получения и установки к классу. Для этого убедитесь, что курсор размещен в определении класса (т.е. между фигурными скобками класса), затем щелкните в редакторе правой кнопкой мыши и выберите пункт "Вставить код" (ALT+INSERT; CTRL+I на компьютерах Mac). Выберите методы получения и установки.
    Всплывающее окно 'Вставить код'
  10. Установите флажок Item (при этом выбираются все свойства, содержащиеся в классе).
    Диалоговое окно 'Создание методов получения и установки'
  11. Нажмите кнопку "Создать". Для класса создаются методы получения и установки.
    public class Item {
    
        private int value;
        private int limit;
    
        public int getLimit() {
            return limit;
        }
    
        public void setLimit(int limit) {
            this.limit = limit;
        }
    
        public int getValue() {
            return value;
        }
    
        public void setValue(int value) {
            this.value = value;
        }
    
        @Override
        public String toString() {
            return super.toString() + String.format(" [Value=%d, Limit=%d]", value, limit);
        }
    }
  12. Создайте конструктор, который принимает оба аргумента value и limit. Кроме того, для этого можно использовать IDE. Нажмите сочетание клавиш CTRL+ПРОБЕЛ в определении класса и выберите параметр "Item(int value, int limit) - generate".
    Список завершения кода, отображенный в редакторе
    К классу добавляется следующий конструктор.
    public class Item {
    
        public Item(int value, int limit) {
            this.value = value;
            this.limit = limit;
        }
    
        private int value;
        private int limit;
    
        ...
  13. Создайте интерфейс ItemDao для определения способа получения списка объектов Item. В этом тестовом приложении мы допускаем использование нескольких реализаций, следовательно, создаем код для интерфейсов.

    Щелкните 'Создать файл' ( Кнопка 'Создать файл' ) или нажмите сочетание клавиш CTRL+N (⌘-N on Mac) для открытия мастера создания файлов.

  14. Выберите категорию Java, а затем команду "Интерфейс Java". Нажмите кнопку "Далее".
  15. Введите ItemDao в качестве имени класса, затем укажите exercise2 в качестве пакета.
  16. Нажмите кнопку "Завершить". Интерфейс будет создан и открыт в редакторе.
  17. Добавьте метод с именем fetchItems(), который возвращает элемент List объектов Item.
    public interface ItemDao {
    
        List<Item> fetchItems();
    
    }
    Используйте подсказку редактора, чтобы добавить оператор импорта для java.util.List.
  18. Создайте класс ItemProcessor. Это главный класс для внедрения базовых элементов и выполнения процесса. базовый элемент.

    Щелкните 'Создать файл' ( Кнопка 'Создать файл' ) или нажмите сочетание клавиш CTRL+N (⌘-N on Mac) для открытия мастера создания файлов.

  19. Выберите категорию Java, а затем выберите класс Java. Нажмите кнопку "Далее".
  20. Введите ItemProcessor в качестве имени класса, затем укажите exercise2 в качестве пакета. Нажмите кнопку "Завершить".

    В редакторе будет создан и открыт новый класс.

  21. Измените класс следующим образом:
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        private ItemDao itemDao;
    
        public void execute() {
            List<Item> items = itemDao.fetchItems();
            for (Item item : items) {
                System.out.println("Found item " + item);
            }
        }
    }
  22. Исправьте операторы импорта. Либо щелкните правой кнопкой мыши в редакторе и выберите 'Исправить выражения импорта' или нажмите Ctrl-Shift-I (⌘-Shift-I в Mac).
    Диалоговое окно 'Исправить выражения импорта'
  23. Нажмите кнопку "ОК". Операторы импорта требуются для следующих классов:
    • java.util.List
    • javax.inject.Named
    • javax.enterprise.context.RequestScoped
  24. Начните с простого DAO, который только создает список элементов и возвращает фиксированный список элементов.

    В окне "Проекты" щелкните правой кнопкой мыши узел пакета exercise2 и выберите "Создать > Класс Java". В мастере создания класса Java присвойте классу имя DefaultItemDao. Нажмите кнопку "Завершить". Мастер классов Java
  25. Необходимо, чтобы в редакторе элемент DefaultItemDao реализовывал интерфейс ItemDao и обеспечивал реализацию fetchItems().
    public class DefaultItemDao implements ItemDao {
    
        @Override
        public List<Item> fetchItems() {
            List<Item> results = new ArrayList<Item>();
            results.add(new Item(34, 7));
            results.add(new Item(4, 37));
            results.add(new Item(24, 19));
            results.add(new Item(89, 32));
            return results;
        }
    }
    Нажмите сочетание клавиш Ctrl-Shift-I (⌘-Shift-I on Mac) для добавления операторов импорта для java.util.List and java.util.ArrayList.
  26. Перейдите к классу ItemProcessor (нажмите сочетание клавиш CTRL+TAB). Чтобы внедрить DefaultItemDao в ItemProcessor добавляется аннотация javax.inject.Inject к полю ItemDao для указания того, что это поле является точкой внедрения.
    import javax.inject.Inject;
    ...
    
    @Named
    @RequestScoped
    public class ItemProcessor {
    
        @Inject
        private ItemDao itemDao;
    
        ...
    }
    Используйте поддержку автозавершения кода редактора для добавления аннотации @Inject и оператора импорта к классу. Например, введите @Inj, а затем нажмите CTRL+ПРОБЕЛ.
  27. Наконец, необходим способ для вызова метода execute() в ItemProcessor. Это можно выполнить в среде SE, но сейчас мы сделаем это на странице JSF. Создайте новую страницу с именем process.xhtml, которая содержит кнопку для вызова метода execute().

    Щелкните 'Создать файл' ( Кнопка 'Создать файл' ) или нажмите сочетание клавиш CTRL+N (⌘-N on Mac) для открытия мастера создания файлов.
  28. Выберите категорию JavaServer Faces, затем выберите страницу JSF. Нажмите кнопку "Далее".
  29. Введите process в качестве имени файла, затем нажмите кнопку "Готово".
    Мастер создания страниц JSF
  30. В новом файле process.xhtml добавьте кнопку, которая привязана к методу ItemProcessor.execute(). При использовании языка выражений имя по умолчанию для управляемого компонента является таким же, как имя класса, но первая буква в нижнем регистре (т.е. itemProcessor).
    <h:body>
        <h:form>
            <h:commandButton action="#{itemProcessor.execute}" value="Execute"/>
        </h:form>
    </h:body>
  31. Перед выполнением проекта установите файл process.xhtml в качестве новой страницы приветствия в дескрипторе развертывания веб-приложения.

    Используйте диалоговое окно среды IDE "Переход к файлу" для быстрого открытия файла web.xml. В основном меню среды IDE выберите "Переход > Перейти к файлу" (ALT+SHIFT+O; CTRL+SHIFT+O на компьютерах Mac), а затем введите web.
    Диалоговое окно 'Перейти к файлу'
  32. Нажмите кнопку "ОК". В представлении XML для файла web.xml выполните следующие изменения.
    <welcome-file-list>
        <welcome-file>faces/process.xhtml</welcome-file>
    </welcome-file-list>
  33. Нажмите кнопку 'Запустить проект' (Кнопка 'Выполнить проект') на главной панели инструментов IDE. Проект компилируется и развертывается на GlassFish, и файл process.xhtml открывается в браузере.
  34. Нажмите кнопку Выполнить на странице. Вернитесь в среду IDE и проверьте протокол сервера GlassFish. Журнал сервера отображается в окне вывода (Ctrl-4; ⌘-4 в Mac) на вкладке 'Сервер GlassFish'. При нажатии кнопки журнал выводит список элементов из реализации DAO по умолчанию.
    Журнал сервера GlassFish в окне вывода
    Щелкните правой кнопкой мыши окно вывода и выберите 'Очистить' (Ctrl-L; ⌘-L в Mac) для очистки журнала. На изображении выше протокол очищен перед нажатием кнопки Выполнить.

Мы создали класс, который реализует интерфейс ItemDao, а при развертывании приложения наши управляемые компоненты в модуле обрабатывались посредством реализации CDI (на основании файла beans.xml в модуле). Наша аннотация @Inject указывает на то, что управляемый компонент необходимо внедрить в это поле, и единственная вещь, которую мы знаем о внедряемом компоненте заключается в том, что он должен реализовывать ItemDao или какой-либо подтип этого интерфейса. В этом случае класс DefaultItemDao полностью отвечает требованиям.

Что может произойти при наличии нескольких реализаций внедряемого интерфейса ItemDao? CDI не сможет определить, какую реализацию необходимо выбрать, и выдаст ошибку во время развертывания. Для устранения этого необходимо использовать квалификатор CDI. Квалификаторы рассматриваются в следующем разделе.


Работа с квалификаторами

Квалификатором CDI является аннотация, которую можно применить на уровне класса, для указания, какой компонент является классом, а также на уровне поля (среди других расположений) для указания, какой компонент требуется для внедрения в этой точке.

Чтобы продемонстрировать необходимость квалификатора в создаваемом приложении, добавим в это приложение другой класс DAO, который также реализует интерфейс ItemDao. На следующей диаграмме показан сценарий, создаваемый в этом упражнении. CDI должен уметь определять, какую реализацию компонента необходимо использовать в точке внедрения. Поскольку существует две реализации интерфейса ItemDao, эта задача решается посредством создания квалификатора с именем Demo. Затем мы "помечаем" используемый компонент, а также точку внедрения в ItemProcessor аннотаций @Demo.

На диаграмме CDI отображаются объекты, созданные в этом учебном курсе

Выполните следующие шаги.

  1. В окне "Проекты" щелкните правой кнопкой мыши пакет exercise2 и выберите "Создать > Класс Java".
  2. В мастере нового класса Java присвойте новому классу имя AnotherItemDao, а затем нажмите «Закончить». В редакторе будет создан и открыт новый класс.
  3. Измените класс, как указано ниже, чтобы он реализовывал интерфейс ItemDao и определял метод fetchItems() интерфейса.
    public class AnotherItemDao implements ItemDao {
    
        @Override
        public List<Item> fetchItems() {
            List<Item> results = new ArrayList<Item>();
            results.add(new Item(99, 9));
            return results;
        }
    }

    Убедитесь, что добавлены операторы импорта для java.util.List и java.util.ArrayList. Для этого щелкните правой кнопкой мыши в редакторе и выберите 'Исправить выражения импорта' или нажмите Ctrl-Shift-I (⌘-Shift-I в Mac).

    Теперь, при наличии двух классов, которые внедряютItemDao, не так ясно, какой базовый элемент необходимо внедрить.

  4. Для запуска проекта нажмите кнопку 'Запустить проект' ( Кнопка 'Выполнить проект' ). Обратите внимание, что теперь развертывание проекта завершается сбоем.

    Возможно, вам просто необходимо сохранить файл - IDE будет автоматически запускать проект, т.к. запуск при сохранении активирован по умолчанию.

  5. Проверьте журнал сервера в окне вывода (Ctrl-4; ⌘-4 в Mac). Отобразится сообщение об ошибке, аналогичное следующему.
    Caused by: org.jboss.weld.DeploymentException: Injection point has ambiguous dependencies.
    Injection point: field exercise2.ItemProcessor.itemDao;
    Qualifiers: [@javax.enterprise.inject.Default()];
    Possible dependencies: [exercise2.DefaultItemDao, exercise2.AnotherItemDao]

    Для переноса текста по словам в окне вывода щелкните правой кнопкой мыши и выберите команду "Перенос по словам". При этом не требуется горизонтальная прокрутка.

    Weld (реализация для CDI) выдает ошибку неоднозначной зависимости, означающую, что невозможно определить компонент, который необходимо использовать для указанной точки внедрения. Большинство ошибок, возникающих при внедрении CDI в Weld, регистрируются во время развертывания, даже если у компонентов в пассивном режиме отсутствует реализация Serializable.

    Полю itemDao в ItemProcessor можно присвоить определенный тип, который соответствует одному из типов реализации (AnotherItemDao или DefaultItemDao). В этом случае этот тип будет соответствовать только одному типу класса. Однако тогда мы потеряем преимущества кодирования интерфейса, а процедура изменения реализаций без изменения типа поля существенно усложнится. Лучшим решением являются квалификаторы CDI.

    Если CDI проверяет точку внедрения для поиска соответствующего внедряемого компонента, то учитывается не только тип класса, но и квалификаторы. Мы уже использовали квалификатор по умолчанию с именем @Any. Теперь создадим квалификатор @Demo, который можно применить для реализации DefaultItemDao, а также для точки внедрения в ItemProcessor.

    IDE предоставляет мастер, позволяющий создавать квалификаторы CDI.

  6. Щелкните 'Создать файл' ( Кнопка 'Создать файл' ) или нажмите сочетание клавиш CTRL+N (⌘-N on Mac) для открытия мастера создания файлов.
  7. Выберите категорию "Внедрение контекстов и зависимостей", затем выберите "Тип "квалификатора". Нажмите кнопку "Далее".
  8. Введите Demo в качестве имени класса, затем укажите exercise2 в качестве пакета.
  9. Нажмите кнопку "Завершить". Новый квалификатор Demo открывается в редакторе.
    package exercise2;
    
    import static java.lang.annotation.ElementType.TYPE;
    import static java.lang.annotation.ElementType.FIELD;
    import static java.lang.annotation.ElementType.PARAMETER;
    import static java.lang.annotation.ElementType.METHOD;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    import javax.inject.Qualifier;
    
    /**
    *
    * @author nbuser
    */
    @Qualifier
    @Retention(RUNTIME)
    @Target({METHOD, FIELD, PARAMETER, TYPE})
    public @interface Demo {
    }

    Затем этот квалификатор будет добавлен к реализатору DAO на уровне класса.

  10. Перейдите в редакторе к DefaultItemDao (нажмите CTRL+TAB), а затем введите "@Demo" над определением класса.
    @Demo
    public class DefaultItemDao implements ItemDao {
    
    @Override
    public List<Item> fetchItems() {
        List<Item> results = new ArrayList<Item>();
        results.add(new Item(34, 7));
        results.add(new Item(4, 37));
        results.add(new Item(24, 19));
        results.add(new Item(89, 32));
        return results;
    }
    }
    После ввода @ нажмите CTRL+ПРОБЕЛ для вызова предложений автозавершения кода. Редактор распознает квалификатор Demo и выводит @Demo в качестве параметра списка для автозавершения кода.
  11. Для запуска проекта нажмите кнопку 'Запустить проект' ( Кнопка 'Выполнить проект' ). Сборка и развертывание проекта выполняются без ошибок.

    Примечание. Для этого изменения может потребоваться явно запустить проект для повторного развертывания приложения вместо развертывания изменений с приращением.

  12. В браузере нажмите кнопку Выполнить, затем вернитесь в среду IDE и проверьте протокол сервера в окне вывода. На экран будет выведено следующее.
    INFO: Found item  [Value=99, Limit=9]

    Выводится позиция из класса AnotherItemDao. Следует помнить о том, что аннотирована реализация DefaultItemDao, но не точка внедрения в ItemProcessor. За счет добавления квалификатора @Demo к реализации DAO по умолчанию другая реализация стала более походящей для точки внедрения, поскольку она соответствует типу и квалификатору. DefaultItemDao имеет в настоящий момент квалификатор Demo, который расположен не в точке внедрения, что делает его менее подходящим.

    Затем вы добавите аннотацию @Demo к точке внедрения вItemProcessor.

  13. Перейдите в редакторе к ItemProcessor (нажмите CTRL+TAB), а затем выполните следующее изменение.
    @Named
    @RequestScoped
    public class ItemProcessor {
    
    @Inject @Demo
    private ItemDao itemDao;
    
    public void execute() {
        List<Item> items = itemDao.fetchItems();
        for (Item item : items) {
            System.out.println("Found item " + item);
        }
    }
    }
  14. В браузере нажмите кнопку Выполнить, затем вернитесь в среду IDE и проверьте протокол сервера в окне вывода. На экран снова выводятся данные реализации по умолчанию (DefaultItemDao).
    INFO: Found item  [Value=34, Limit=7]
    INFO: Found item  [Value=4, Limit=37]
    INFO: Found item  [Value=24, Limit=19]
    INFO: Found item  [Value=89, Limit=32]

    Это произошло из-за сопоставления на основе типаиквалификаторов, аDefaultItemDaoявляется единственным базовым элементом правильного типа и с аннотацией@Demo.


Альтернативные методы внедрения

Существует несколько методов для определения точки внедрения во внедряемом классе. Пока вы проставили аннотации к полям, которые ссылаются на внедренный объект. Для внедрения поля не требуется методы получения и установки. Если вы хотите создать неизменяемые управляемые базовые элементы с окончательными полями, то можете использовать внедрение в конструкторе с помощью применения к конструктору аннотации @Inject. Затем вы можете применять любые аннотации к параметрам конструктора с целью квалификации базовых элементов для внедрения. (Разумеется, каждый параметр имеет тип, который может помочь квалифицировать компоненты для внедрения.) Компонент может иметь только один конструктор с определенными точками внедрения, но он может реализовать более одного конструктора.

@Named
@RequestScoped
public class ItemProcessor {

    private final ItemDao itemDao;

    @Inject
    public ItemProcessor(@Demo ItemDao itemDao) {
        this.itemDao = itemDao;
    }
}

Вы также можете вызвать метод инициализации, который может быть передан базовому элементу для внедрения.

@Named
@RequestScoped
public class ItemProcessor {

    private ItemDao itemDao;

    @Inject
    public void setItemDao(@Demo ItemDao itemDao) {
        this.itemDao = itemDao;
    }
}

Хотя в вышеприведенном случае для инициализации используется метод установки, вы можете создать любой метод и использовать его для инициализации любого количества базовых элементов при вызове метода. Вы также можете использовать несколько методов инициализации для одного базового элемента.

@Inject
public void initBeans(@Demo ItemDao itemDao, @SomeQualifier SomeType someBean) {
    this.itemDao = itemDao;
    this.bean = someBean;
}

Аналогичные правила применяются для сопоставления компонентов независимо от способа определения точки внедрения. CDI пытается найти лучшее соответствие на основе типа и квалификаторов и выдает сбой развертывания при наличии нескольких соответствующих компонентов или при отсутствии соответствующих компонентов для точки внедрения.


Дополнительные сведения

Перейдите к следующему разделу этой серии для внедрения контекстов и зависимостей:

Дополнительные сведения о CDI и Java EE приведены в следующих материалах.

get support for the NetBeans

Support


By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo