Статьи

CDI — обзор: часть 1

Я могу быть фанатом Spring, но я также убежден, что технология должна соответствовать стандартам. Хотя Spring сейчас является стандартом де-факто, он конкурирует с другими продуктами, такими как, например, Google Guice. Это усложняет мою работу в качестве архитектора, поскольку моя задача состоит в том, чтобы разрабатывать самые постоянные решения: стандарты — мои союзники в достижении этой цели.

CDI , ранее известный как JSR 299, является попыткой описать истинный стандарт внедрения зависимостей. Что делает CDI привлекательным на первый взгляд, так это то, что SpringSource и Google заняли место в команде разработчиков спецификаций. CDI является частью стека Java EE 6, то есть приложение, работающее в контейнере, совместимом с Java EE 6, может использовать CDI «из коробки».

Таким образом, для приложения Maven все, что требуется, это добавить следующую зависимость:

<dependency>
<groupId>javax.enterprise</groupId>
<artifactId>cdi-api</artifactId>
<version>1.0-SP1</version>
<scope>provided</scope>
</dependency>

Основы

Как это сделать? Давайте возьмем простой пример. Я все еще хочу использовать облегченный сервисный уровень: мне просто нужна ссылка на такой сервис в моем сервлете. Для этого просто добавьте ссылку на сервис в качестве атрибута и аннотируйте его @Inject:

public class WeldServlet extends HttpServlet {

@Inject private HelloService helloService;

...
}

Примечание для пользователей Guice: обратите внимание, что это та же самая аннотация, только в стандартном пакете (javax.enterprise).

Вот и все! Нет причудливой конфигурации XML, нет ресурсов JNDI для использования, нет ничего. Единственное, что нужно сделать, это следовать тому, что я называю правилом Горца:

Там может быть только один.

Это правило предписывает, что в пути к классам должен быть один и только один класс, который расширяет HelloService, тем самым выполняя контракт интерфейса. Это здравый смысл: внедрение должно быть детерминированным, и вы не можете этого сделать, если у вас есть более одной реализации на выбор.

активация

По правде говоря, если вы просто используете аннотацию @Inject, вы, вероятно, столкнетесь с NullPointerException. Для активации CDI вам понадобится файл конфигурации XML с именем beans.xml в разделе WEB-INF для веб-приложений или META-INF для файлов jar. Этот файл может быть пустым, но является обязательным для загрузки CDI.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
</beans>

Отборочные

Тем не менее, такое условие не выполняется в большинстве случаев, так как у вас, вероятно, будет по крайней мере другая фиктивная реализация для ваших модульных тестов. Где-то отсутствует понятие: это классификатор. Классификаторы добавляют качество к инъекции, так что правило Highlander применяется между всеми классами, которые соответствуют всем квалификаторам инъекций. Таким образом, можно добавить отличительный квалификатор к сервису макета, чтобы решить нашу проблему. Давайте разработаем такой классификатор:

  • чтобы считаться классификатором, он использует аннотацию @Qualifier
  • поскольку внедрение выполняется во время выполнения, политика хранения должна быть во время выполнения
  • target будет type, поскольку сам класс будет аннотирован

Результат следующий:

@Qualifier
@Retention(RUNTIME)
@Target( { TYPE })
public @interface Mock {}

Просто аннотируйте свой фиктивный сервис с помощью @Mock и presto, инъекция снова будет успешной. Мы не видели, как внедрить поддельную услугу, но, пожалуйста, потерпите меня, это будет решено позже.

Сеттер впрыска

На самом деле, такой ситуации не должно быть (по крайней мере, если вы используете Maven), поскольку ваш стандартный путь к классу и путь к классу тестирования должны отличаться. Более того, ваш юнит-тест не должен использовать инъекции. Это немного изменило бы мой сервлет, чтобы он использовался как в стандартном контексте, так и в контексте модульного тестирования: мне нужно будет ввести в первом случае и установить вручную во втором. Это не проблема, так как CDI также принимает инъекцию сеттера, как в следующем фрагменте:

public class WeldServlet extends HttpServlet {

private HelloService helloService;

@Inject
public void setHelloService(HelloService helloService) {

this.helloService = helloService;
}
...
}

Больше квалификаторов

Как мы видели, описанный выше вариант использования квалификатора не был хорошим примером. Гораздо лучше было бы, если бы сервлет сообщал об ошибке. Есть много способов сообщить об этом: почта, журнал, SMS и т. Д. Служба, используемая для отчета, будет зависеть от сервлета, то есть все службы должны быть доступны на пути к классам. Теперь, как мы уже видели в @Mock, каждый сервис будет аннотирован @Mail, @Log, @SMS и т. Д. Мы не увидели, как внедрить правильный сервис. Нет ничего проще, просто скажите CDI, какая услуга вам нужна, предоставив необходимый классификатор:

public class WeldServlet extends HttpServlet {

@Inject @Mail private ReportService reportService;

...
}

Если вы не определите какой-либо квалификатор, CDI будет использовать его для вас под прикрытием @Default. Вот почему просто аннотировать сервис-макет с помощью @Mock удалось: реальный сервис был неявно аннотирован с помощью @Default, и этого было достаточно для выполнения правила Higlander.

Квалификаторы с атрибутами

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

public enum ReportType {

MAIL, SMS, LOG; // Adding one more type here is easy
}

@Qualifier
@Retention(RUNTIME)
@Target( { TYPE, FIELD })
public @interface Report {
ReportType value();
}

@Report(MAIL)
public class MailReportServiceImpl implements ReportService {...}

public class WeldServlet extends HttpServlet {

@Inject @Report(MAIL)
private ReportService reportService;
...
}

Создание другой службы отчетов состоит из создания самой реализации службы и добавления значения в перечисление.

Одиночки

Традиционно, сервисы не имеют состояния и, как таковые, мало заинтересованы в том, чтобы их создавали более одного раза: это хорошая практика — делать их одиночными. Фреймворки, такие как Spring, по умолчанию делают управляемые контейнером компоненты-бобы. Создание синглетонов — это особенность CDI, но имейте в виду, что синглтоны должны быть явно помечены так:

@Singleton
public class HelloServiceImpl implements HelloService {
...
}

Scoped бобы

Синглтоны — это только частный случай области видимости. В Java EE области действия четко определены в отношении веб-приложений: приложения, сеанса, запроса и страницы. CDI не управляет областью страницы и добавляет область диалога, связанную с JSF. Использование области в CDI аналогично Spring: внедренный компонент будет привязан к определенной области, и его видимость ограничена этой областью.

Информация, относящаяся к настройкам и предпочтениям, вполне может иметь место, например, в bean-объекте в области сеанса. Просто добавьте правильную аннотацию области действия к этому бобу:

@SessionScoped
public class UserSettings implements Serializable {

private static final long serialVersionUID = 1L;
...
}

На данный момент, вы, вероятно, должны быть в состоянии удовлетворить 80% ваших потребностей. Следующая статья будет касаться продвинутых тем, таких как производители и альтернативы.

Чтобы идти дальше:

  • Страница аннотаций Commons (JSR-250) : аннотации Commons содержат аннотацию для DI в Java (@Resource)
  • Страница CDI (JSR-299) : достаточно удивительно, CDI о DI в Java EE
  • Weld в документации : Weld является реализация CDI JBoss , а также эталонная реализация
  • Статья о достоинствах JSR-299 по сравнению с достоинствами JSR-330

 

От http://blog.frankel.ch/cdi-an-overview-part-1