Это второй пост о внедрении зависимостей в CDI (Часть 1) после нашего разговора о том, как начать работу с CDI в вашем окружении и как интегрировать CDI в существующее Java EE 6 приложение. В этом посте я хочу рассказать о различных точках внедрения в CDI: поле, конструктор и сеттер. Для этого я буду использовать часть предыдущего примера: внедрение POJO генератора ISBN в сервлет.
Внедрение через поле
Во всех предыдущих примерах вы видели аннотации @Inject
, привязанные к полям (атрибутам) класса.
@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {
@Inject
@ThirteenDigits
private NumberGenerator numberGenerator;
@Inject
private ItemEJB itemEJB;
...
}
Как вы видите в представленном коде, аннотацией @Inject
и спецификатором (здесь @ThirteenDigits
) помечен атрибут. Но, как и во многих других фреймворках по внедрению зависимостей, в CDI вы можете проводить внедрение через конструктор или сеттер.
Внедрение через конструктор
Вместо атрибутов вы можете добавить аннотацию @Inject
к конструктору. Если же вам необходимо специфированное внедрение, то вы можете пометить спецификатором параметр конструктора:
@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {
private NumberGenerator numberGenerator;
private ItemEJB itemEJB;
@Inject
public ItemServlet(@ThirteenDigits NumberGenerator numberGenerator, ItemEJB itemEJB) {
this.numberGenerator = numberGenerator;
this.itemEJB = itemEJB;
}
...
}
Как вы видите, в данном случае аннотацией @Inject
помечен не атрибут класса, а конструктор. С другой стороны, @ThirteenDigits
помечает не конструктор, а его параметр numberGenerator
(что логично). Если захотите, вы можете смешивать внедрение через поле и через конструктор (ниже я использую внедрение через конструктор и атрибут для EJB):
@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {
private NumberGenerator numberGenerator;
@Inject
private ItemEJB itemEJB;
@Inject
public ItemServlet(@ThirteenDigits NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}
...
}
Но есть правило: у вас может быть только один конструктор с внедрением. Контейнер выполняет внедрение, не вы (вы, конечно, можете вызывать конструктор в управляемой среде, но он не будет работать так, как вы ожидаете). И есть только один конструктор бина, позволяющий контейнеру выполнить корректное внедрение всех зависимостей. Следующий код некорректный:
@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {
private NumberGenerator numberGenerator;
private ItemEJB itemEJB;
@Inject
public ItemServlet(@ThirteenDigits NumberGenerator numberGenerator, ItemEJB itemEJB) {
this.numberGenerator = numberGenerator;
this.itemEJB = itemEJB;
}
@Inject
public ItemServlet(@ThirteenDigits NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}
...
}
Если у вас более одного конструктора в бине, вот что вы получите (код и сообщение об ошибке, конечно, специфичны для Weld)
WELD-000812 Cannot determine constructor to use for public@WebServlet class ItemServlet. Possible constructors [[constructor] @Inject public ItemServlet(NumberGenerator, ItemEJB), [constructor] @Inject public ItemServlet(NumberGenerator)]
Да, синтаксически допустимо проводить внедрение через поле и конструктор одновременно, но смысла в этом нет:
@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {
@Inject @ThirteenDigits
private NumberGenerator numberGenerator;
@Inject
private ItemEJB itemEJB;
@Inject
public ItemServlet(@ThirteenDigits NumberGenerator numberGenerator, ItemEJB itemEJB) {
this.numberGenerator = numberGenerator;
this.itemEJB = itemEJB;
}
...
}
Внедрение через сеттер
Есть другой способ — это использовать внедрение через сеттер, которое выглядит как внедрение через конструктор. Аннотацией @Inject
вы помечаете сам сеттер, а спецификаторами — его аргументы:
@WebServlet(urlPatterns = "/itemServlet")
public class ItemServlet extends HttpServlet {
private NumberGenerator numberGenerator;
private ItemEJB itemEJB;
@Inject
public void setNumberGenerator(@ThirteenDigits NumberGenerator numberGenerator) {
this.numberGenerator = numberGenerator;
}
@Inject
public void setItemEJB(ItemEJB itemEJB) {
this.itemEJB = itemEJB;
}
...
}
Когда вы используете внедрение через конструктор или сеттер, вам необходимо специфицировать аргументы. Поэтому убедитесь, что у вас правильно объявлен @Target(java.lang.annotation.ElementType.PARAMETER)
:
@Qualifier
@Retention(RUNTIME)
@Target({FIELD, TYPE, METHOD, PARAMETER})
public @interface ThirteenDigits {
}
Заключение
У вас может возникнуть один вопрос (и я его тоже задал Pete Muir): когда нужно использовать ту или иную точку внедрения? Но на этот вопрос нет технического ответа, это дело личного вкуса. В управляемой среде только контейнер выполняет внедрение, и всё, что ему нужно — это корректные точки внедрения. Однако в случае внедрения через конструктор или сеттер, при необходимости вы можете добавить какую-то логику (что невозможно при внедрении через атрибуты). Но, похоже, что внедрение через сеттеры было добавлено, скорее, для обратной совместимости с уже созданными Java Beans.
В следующей статье я расскажу о продюсерах.
Исходный код
Скачайте код и расскажите, что вы о нем думаете.