Внедрение зависимостей в CDI. Часть 2

в 16:39, , рубрики: cdi, dependency injection, java, javaee, Блог компании AT Consulting, Программирование

Это второй пост о внедрении зависимостей в CDI (Часть 1) после нашего разговора о том, как начать работу с CDI в вашем окружении и как интегрировать CDI в существующее Java EE 6 приложение. В этом посте я хочу рассказать о различных точках внедрения в CDI: поле, конструктор и сеттер. Для этого я буду использовать часть предыдущего примера: внедрение POJO генератора ISBN в сервлет.

COFFEE_BEANS

Внедрение через поле

Во всех предыдущих примерах вы видели аннотации @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.

В следующей статье я расскажу о продюсерах.

Исходный код

Скачайте код и расскажите, что вы о нем думаете.

Автор: AT Consulting

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js