Поскольку Java EE является управляемой средой, вам не придется конструировать зависимости вручную. Вместо вас ссылку может внедрить контейнер. Одним словом, внедрение зависимостей CDI — это возможность внедрять одни компоненты в другие с сохранением безопасности типов, что означает использование XML вместо аннотаций.
Внедрение уже существовало в Java EE 5 с такими аннотациями, как @Resource, @PersistentUnit и EJB. Но оно было ограничено до определенных ресурсов (баз данных, архитектура EJB) и компонентов (сервлетов, компонентов EJB, связующих компонентов JSF и т. д.). С помощью CDI вы можете внедрить практически что угодно и куда угодно благодаря аннотации @Inject. Обратите внимание, что в Java EE 7 разрешено использовать другие механизмы внедрения (@Resource), однако лучше стараться применять @Inject везде, где это возможно (см. подраздел «Производители данных» раздела «Создание компонента CDI» этой главы).
Листинг 2.4 показывает, как внедрять в BookService ссылку на NumberGenerator с помощью объекта CDI.
public class BookService {
··@Inject
··private NumberGenerator numberGenerator;
··public Book createBook(String title, Float price, String description) {
····Book book = new Book(title, price, description);
····book.setIsbn(numberGenerator.generateNumber());
····return book;
··}
}
Как видно из листинга 2.4, простая аннотация @Inject у атрибута сообщит контейнеру, что ему необходимо внедрить ссылку на реализацию NumberGenerator, относящуюся к свойству NumberGenerator. Это называется точкой внедрения (место, где находится аннотация @Inject). Листинг 2.5 показывает реализацию IsbnGenerator. Как видите, здесь нет специальных аннотаций, а класс реализует интерфейс NumberGenerator.
public class IsbnGenerator implements NumberGenerator {
··public String generateNumber() {
····return "13-84356-" + Math.abs(new Random(). nextInt());
··}
}
Во время инстанцирования компонента происходит внедрение в той точке, которая указана в аннотации @Inject. Внедрение может происходить с помощью трех различных механизмов: свойства, сеттера или конструктора.
Во всех предыдущих примерах кода вы видели аннотацию @Inject к атрибутам (свойствам):
@Inject
private NumberGenerator numberGenerator;
Обратите внимание, что не обязательно создавать метод геттера и сеттера для атрибута, чтобы использовать внедрение. CDI может получить прямой доступ к полю с внедренным кодом (даже если оно приватное), что иногда помогает исключить лишний код. Но вместо аннотирования атрибутов вы можете добавить аннотацию @Inject к конструктору, как показано ниже:
@Inject
public BookService (NumberGenerator numberGenerator) {
··this.numberGenerator = numberGenerator;
}
Однако существует правило, что вы можете иметь только одну точку внедрения конструктора. Именно контейнер (а не вы) выполняет внедрение, так как вы не можете вызвать конструктор в управляемой среде. Поэтому для того, чтобы контейнер мог выполнить свою работу и внедрить правильные ссылки, разрешается только один конструктор компонентов.
Другой вариант — использовать внедрение сеттера, что выглядит как внедрение конструктора. Вам просто нужно добавить к сеттеру аннотацию @Inject:
@Inject
public void setNumberGenerator(NumberGenerator numberGenerator) {
··this.numberGenerator = numberGenerator;
}
Вы можете спросить, когда нужно использовать поле вместо конструктора или внедрения сеттера. На этот вопрос не существует ответа с технической точки зрения, это скорее дело вашего личного вкуса. В управляемой среде только контейнер выполняет всю работу по внедрению, необходимо лишь задать правильные точки внедрения.
Предположим, NumberGenerator имеет только одну реализацию (IsbnGenearator). Тогда CDI сможет внедрить его, просто самостоятельно используя аннотацию @Inject:
@Inject
private NumberGenerator numberGenerator;
Это называется внедрением по умолчанию. Каждый раз, когда компонент или точка внедрения не объявляет очевидным образом квалификатор, контейнер по умолчанию использует квалификатор @javax.enterprise.inject.Default. На самом деле предыдущему отрывку кода идентичен следующий: