Статьи

Инъекция с CDI (часть I)

скачатьПосле того, как я напишу пост о том, как загрузить CDI в вашей среде, и дам несколько советов о том, как включить CDI в существующее приложение Java EE 6 , я хочу поговорить о внедрении. Да, чистая инъекция или как ввести боб в другой . Как вы увидите в этой серии из двух статей , может случиться много разных случаев. Давайте начнем с простого: прямой впрыск.

Инъекция по умолчанию

Самым простым случаем инъекции является … простой. У вас есть что-то , и вы вводите  что-то в это. Почему я использую слово  что-то ? Потому что до Java EE 5 мы могли только внедрять ресурсы (EntityManager, Datasource, JMS-адреса и фабрики …) в определенные компоненты (EJB и сервлеты). С CDI вы можете вводить практически все что угодно .

Версии программного обеспечения, используемого для этой статьи
Java SE 1.6.0_23
GlassFish 3.1
Maven 3.0.2

Чтобы проиллюстрировать внедрение, я буду использовать тот же вариант использования, который использовался в предыдущих статьях или в моей книге по Java EE 6 : магазин CD-Book.

Диаграмма классов выше показывает следующие компоненты:

  • Книга — это просто сущность с некоторыми атрибутами и именованными запросами
  • ItemEJB — это EJB без состояния (без интерфейса), выполняющий операции CRUD в Книге благодаря EntityManager.
  • IsbnGenerator — это просто POJO, который генерирует случайный номер ISBN (используется для Книги)
  • ItemRestService аннотируется @Path (который обозначает веб-сервис REST в JAX-RS) и делегирует операцию CRUD ItemEJB
  • ItemServlet — это сервлет, который использует ItemEJB для отображения всех книг из базы данных.

Как вы можете видеть, за исключением EntityManager, который внедряется с помощью @PersistenceContext, все остальные компоненты используют @Inject. Вот несколько строк кода ItemEJB, получающего ссылку на EntityManager:

@Stateless
public class ItemEJB {

@PersistenceContext(unitName = "cdiPU")
private EntityManager em;
...
}

ItemServlet и ItemRestService очень похожи, поскольку они оба вводят ссылку на ItemEJB и IsbnGenerator:

@Stateless
public class ItemEJB {

@PersistenceContext(unitName = "cdiPU")
private EntityManager em;
...
}

И у IsbnGenertor нет абсолютно ничего особенного, так как он ни от чего не вытекает и не аннотируется, это просто POJO:

 

public class IsbnGenerator {

public String generateNumber () {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}

Во всех этих случаях можно выбрать только одну реализацию (есть только ItemEJB, только один IsbnGenerator). Если у вас есть только одна реализация, CDI сможет внедрить ее. Затем мы поговорим о внедрении по умолчанию . На самом деле код:

@Inject IsbnGenerator numberGenerator

мог быть написан

@Inject IsbnGenerator numberGenerator

@Default — это встроенный квалификатор, который информирует CDI о необходимости внедрения реализации компонента по умолчанию. Если вы определяете бин без квалификатора, у боба автоматически появляется классификатор @Default. Следующий код идентичен предыдущему.

@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {

@Inject @Default
private IsbnGenerator numberGenerator;
...
}

@Default
public class IsbnGenerator {

public String generateNumber () {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}

Если у вас есть только одна реализация IsbnGenerator для внедрения, применяется поведение по умолчанию и прямой @Inject делает свою работу. Но иногда вам приходится выбирать между несколькими реализациями, вот где вступают в игру квалификаторы .

В этой статье я использую термин bean , но, чтобы быть более точным, я должен сказать управляемый bean (то есть bean, который управляется CDI ). ManagedBeans были введены в Java EE 6.

Неоднозначная инъекция и классификаторы

Для данного типа бина может быть несколько бинов, которые реализуют этот тип . Например, наше приложение может иметь две реализации интерфейса NumberGenerator: IsbnGenerator генерирует 13-значный номер и IssnGenerator 8-значный номер. Компонент, который должен сгенерировать 13-значный номер, должен каким-то образом различать две реализации. Один из подходов заключается в явном указании класса (IsbnGenerator), но это создает жесткую зависимость между компонентом и реализацией. Другой подход заключается в том, чтобы полагаться на внешнюю конфигурацию XML для объявления и внедрения соответствующего компонента. CDI использует квалификаторы, которые представляют собой аннотации, для строгой типизации и слабой связи .

В этой статье я использую инъекцию для атрибута, это означает, что аннотация @Inject находится на атрибуте. Но с CDI вы также можете использовать инъекцию сеттера или конструктора.

Предположим, по какой-то причине, что ItemServlet создает книги с ISBN-номером из 13 цифр, а ItemRestService создает книги с ISSN-номером из 8 цифр. Оба (ItemServlet и ItemRestService) вводят ссылку на один и тот же интерфейс NumberGenerator, какую реализацию он будет использовать? Ты не знаешь? CDI тоже не знает, и вы получите сообщение об ошибке:

Ambiguous dependencies for type [NumberGenerator] with qualifiers [@Default] at injection point [[field] 
@Inject private ItemRestService.numberGenerator]. Possible dependencies [[Managed Bean [class IsbnGenerator]
with qualifiers [@Any @Default], Managed Bean [class IssnGenerator] with qualifiers [@Any @Default]]].

Это означает, что мы должны быть менее двусмысленными и сообщать CDI, какой бин и куда внедрять. Если вы пришли из Spring, первое, что приходит вам в голову, это « давайте использовать файл beans.xml ». Но, как говорится в этом посте , « beans.xml не предназначен для определения bean в XML ». С CDI вы используете классификаторы (аннотации).

В CDI есть три встроенных классификатора :

  • @Default: если бин не объявляет спецификатор явно, бин имеет спецификатор @Default
  • @Any: позволяет приложению динамически указывать классификаторы
  • @New: позволяет приложению получить новый квалифицированный компонент

Спецификатор представляет некоторую семантику, связанную с типом, которая удовлетворяется некоторыми реализациями типа. Например, вы можете ввести квалификаторы для представления генератора чисел из 13 цифр или генератора чисел из восьми цифр . В Java квалификаторы представлены аннотациями, определенными как @Target ({FIELD, TYPE, METHOD}) и @Retention (RUNTIME). Это объявляется путем указания метааннотации @ javax.inject.Qualifier следующим образом:

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface EightDigits {
}

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface ThirteenDigits {
}

Как видите, я только что определил два классификатора, очень легко. Хорошо, так как я могу использовать их сейчас? Лучше, чем слова, диаграмма классов прояснит это.

Прежде всего, квалификаторы должны быть применены к соответствующей реализации. Как видите, @ThirteenDigits применяется к IsbnGenerator, а @EightDigits — к @IssnGenerator:

@EightDigits
public class IssnGenerator implements NumberGenerator {

public String generateNumber() {
return "8-" + Math.abs(new Random().nextInt());
}
}

@ThirteenDigits
public class IsbnGenerator implements NumberGenerator {

public String generateNumber() {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}

Затем компоненты, которые вставляют ссылку на интерфейс NumberGenerator, также должны использовать его следующим образом:

@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {

@Inject @ThirteenDigits
private NumberGenerator numberGenerator;
...
}

@Path("/items")
@ManagedBean
public class ItemRestService {

@Inject @EightDigits
private NumberGenerator numberGenerator;
...
}

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

Квалификаторы с перечислениями

Каждый раз, когда вам нужно выбрать между реализацией, вы создаете аннотацию. Поэтому, если вам нужен дополнительный  двухзначный генератор чисел или  десятизначный генератор чисел, вы создаете все больше и больше аннотаций. Похоже, мы переходим от XML Hell к Annotation Hell! Одним из способов избежать размножения аннотаций является использование перечисления следующим образом:

@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {

@Inject @NumberOfDigits(Digits.THIRTEEN)
private NumberGenerator numberGenerator;
...
}

@Path("/items")
@ManagedBean
public class ItemRestService {

@Inject @NumberOfDigits(Digits.EIGHT)
private NumberGenerator numberGenerator;
...
}

@NumberOfDigits(Digits.THIRTEEN)
public class IsbnGenerator implements NumberGenerator {

public String generateNumber() {
return "13-84356-" + Math.abs(new Random().nextInt());
}
}

@NumberOfDigits(Digits.EIGHT)
public class IssnGenerator implements NumberGenerator {

public String generateNumber() {
return "8-" + Math.abs(new Random().nextInt());
}
}

Как вы можете видеть, я избавился от спецификаторов @ThirteenDigits и @EightDigits, и я использую один квалификатор @NumberOfDigits, который является перечислением в качестве значения (в моем примере @EightDigits). Это код, который вам нужно написать:

@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD})
public @interface NumberOfDigits {
Digits value();
}

public enum Digits {
TWO,
EIGHT,
TEN,
THIRTEEN
}

Вывод

Я не знаю как вы, но мне это нравится. Мне очень нравится, как CDI связывает бины вместе без XML , просто с чистой Java безопасным для типов способом . Хорошо, хорошо, я признаю, не все красиво. Первое, что я вижу, это умножение аннотаций в вашем коде. Благодаря перечислениям это может быть ограничено. Другой момент, который я вижу, это поддержка IDE . Ваша IDE должна быть умной, чтобы знать, что:

ссылается на IssnGenerator. Но здесь я говорю, не особо разбираясь в теме. Я использую Intellij IDEA, и поддержка CDI просто потрясающая. Я могу перемещаться от бина к бину, не беспокоясь о знании реализации. Я предполагаю, что NetBeans может иметь какую-то поддержку … но мне интересно, есть ли в Eclipse; o)

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

Скачать

Загрузите код , попробуйте и дайте мне обратную связь.

Рекомендации