Если вы следите за этим блогом, то помните, что в последнее время я пишу (и говорю) о CDI (Contexts and Dependency Injection). У CDI много аспектов, но до сих пор я акцентировал внимание на том, как начать работу с CDI в вашем окружении и как интегрировать CDI в существующее Java EE 6 приложение, а затем сфокусировался на внедрении зависимостей в CDI. Это уже третий пост про внедрение в CDI: в первом я рассказывал о внедрении по умолчанию и спецификаторах, во второй о всех возможных точках внедрения (поле, конструктор, сеттер). В этом посте я расскажу о продюсерах или "как вы можете типобезопасным способом внедрять что угодно и куда угодно".

Внедряем только бины?
До этого я показывал вам, как внедрять бины с помощью обычной аннотации @Inject. Если вы посмотрите на пример с генерацией номера книги, который я использовал ранее, то увидите сервлет и rest сервис, в которые внедряется реализация интерфейса NumberGenerator. Благодаря спецификаторам, сервлет может получить IsbnGenerator, помеченный в точке внедрения спецификатором @ThirteenDigit, а rest сервис получит IssnGenerator, помеченный @EightDigits (смотрите мой первый пост). Представленная диаграмма классов демонстрирует некоторую комбинацию внедрения бинов:

Но как вы видите, всё это внедрение бинов в другие бины. В CDI мы можем внедрять только бины? Нет. Вы можете внедрять что угодно и куда угодно.
Продюсеры
Да, вы можете внедрять что угодно и куда угодно. Всё, что вам нужно для этого сделать, это создать что-то, что вы хотите потом внедрить. Для этого в CDI есть продюсеры (отличная реализация паттерна Фабрика). С их помощью можно создать:
- Класс: неограниченное множество типов бина, его суперклассов и всех реализуемых им интерфейсов
- Интерфейс: неограниченное множество типов бина, рассширенные им интерфейсы, а также
java.lang.Object - Примитивы и Java массив
Так что вы можете внедрять java.util.Date, java.lang.String или даже int. Начнем с создания и внедрения некоторых типов данных и примитивов.
Создание типов данных и примитивных типов
Один из примеров того, как внедрить что угодно и куда угодно, это внедрение типов данных и примитивов. Ну что, давайте попробуем внедрить String и int. Для этого мне нужно добавить дополнительные классы в нашу модель. До этого IsbnGenerator создавал случайное число в формате 13-124-454644-4. Этот номер состоит из строки, выступающей в роли префикса (13-124), и целого значения — постфикса (4). Следующая диаграмма классов показывает два новых класса PrefixGenerator и PostfixGenerator, которые будут использоваться для генерации номера:

Если мы посмотрим, например на код PrefixGenerator, то увидим класс, который сам не помечен никаким спецификатором, в отличие от его методов. Метод getIsbnPrefix возвращает строку, которая специфицирована аннотацией @ThirteenDigits. Эта строка создается с помощью CDI (благодаря @Produces), а это значит, что вы можете внедрять её с помощью @Inject и её спецификатора (@ThirteenDigits).
public class PrefixGenerator {
@Produces @ThirteenDigits
public String getIsbnPrefix() {
return "13-84356";
}
@Produces @EightDigits
public String getIssnPrefix() {
return "8";
}
}А теперь внимательно посмотрите на класс PostfixGenerator. Этот код аналогичен предыдущему, за исключением того, что он создает int, который потом может быть внедрен.
public class PostfixGenerator {
@Produces @ThirteenDigits
public int getIsbnPostfix() {
return 13;
}
@Produces @EightDigits
public int getIssnPostfix() {
return 8;
}
}А теперь поменяем логику генерации ISBN номера. В бин IsbnGenerator внедряются String и int c помощью @Inject и @ThirteenDigits. Теперь вы понимаете, что значит строгая типизация в CDI. Используя этот синтаксис (@Inject @ThirteenDigits), CDI знает, что нужно внедрить на место String, а что на место int и какую использовать реализацию NumberGenerator.
@ThirteenDigits
public class IsbnGenerator implements NumberGenerator {
@Inject @ThirteenDigits
private String prefix;
@Inject @ThirteenDigits
private int postfix;
public String generateNumber() {
return prefix + "-" + Math.abs(new Random().nextInt()) + "-" + postfix;
}
}Внедрение ресурсов Java EE через поля продюсеров
Итак, если CDI может внедрять что угодно и куда угодно, если он даже умеет внедрять строки и целые числа, то как насчет ресурсов Java EE? Это другая история, и продюсеры нам помогут в этом. Как я уже говорил ранее, в CDI всё типобезопасно, так что в CDI нет никакого способа внедрить ресурс по его JNDI-имени, например, так: @Inject(name="jms/OrderQueue"). Стандартный пример — менеджер сущностей. Если вы не используете CDI, то в Java EE 6 вы можете внедрить его следующим образом:
@Stateless
public class ItemEJB {
@PersistenceContext(unitName = "cdiPU")
private EntityManager em;
...
}Так почему же невозможно сделать обычный @Inject для менеджера сущностей? Если вы помните мой первый пост о неоднозначности внедрения, то тут та же проблема. У вас может быть несколько единиц персистентности (именованных обычной строкой), и если вы просто напишите @Inject, то CDI не сможет понять, какой именно из них нужно внедрить. Вместо этого вы должны создавать менеджер сущностей первым и как-то его именовать (если не хотите использовать @Default):
public class DatabaseProducer {
@Produces
@PersistenceContext(unitName = "cdiPU")
@BookStoreDatabase
private EntityManager em;
}Класс DatabaseProducer использует @PersistenceContext для внедрения менеджера сущностей с единицей персистентности cdiPU. Он будет помечен спецификатором (@BookStoreDatabase) и инициализирован, после чего вы можете внедрять его в EJB следующим образом:
@Stateless
public class ItemEJB {
@Inject
@BookStoreDatabase
private EntityManager em;
...
}Другой хороший пример — это создание фабрик JMS и адресатов. Аннотация @Resource позволяет вам получить JNDI-ссылку на JMS-фабрику или адресата, спецификатор @Order именует её, а @Produces позволяет в дальнейшем её внедрить:
public class JMSResourceProducer {
@Produces @Order @Resource(name = "jms/OrderConnectionFactory")
private QueueConnectionFactory orderConnectionFactory;
@Produces @Order @Resource(name = "jms/OrderQueue")
private Queue orderQueue;
}Теперь ваш EJB может использовать типобезопасный @Inject:
@Stateless
public class ItemEJB {
@Inject @Order
private QueueConnectionFactory orderConnectionFactory;
@Inject @Order
private Queue orderQueue;
...
}Создание Java EE ресурсов с помощью методов продюсеров
Рассмотренные примеры достаточно простые: создаем поле и затем можем его внедрять. Это называется продюсирующим полем. Но иногда вам необходима более сложная бизнес-логика создания бина, и это мы будем называть методом-продюсером. Давайте посмотрим на другой пример. Когда вам нужно отправить JMS-сообщение, вы всегда внедряете JMS-фабрику и адресата (очередь или топик), создаете соединение, потом сессиию и так далее, пока не отправите сообщение. Поскольку этот код часто повторяется и может содержать ошибки, то почему бы его не вынести куда-нибудь в отдельный класс и не создавать с его помощью сессию и другие компоненты, необходимые для отправки сообщения? Этот класс мог бы выглядеть, например, так:
public class JMSResourceProducer {
@Resource(name = "jms/OrderConnectionFactory")
private QueueConnectionFactory orderConnectionFactory;
@Produces @Order @Resource(name = "jms/OrderQueue")
private Queue orderQueue;
@Produces @Order
public QueueConnection createOrderConnection() throws JMSException {
return orderConnectionFactory.createQueueConnection();
}
@Produces @Order
public QueueSession createOrderSession(@Order QueueConnection conn) throws JMSException {
return conn.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
}
}Во-первых, класс использует @Resource для получения ссылок на QueueConnectionFactory и Queue. По тем же причинам, которые я описывал выше, у вас может быть несколько JMS-фабрик и адресатов, и вы должны различать их по JNDI-имени. И так как CDI не позволяет указывать имя в точке внедрения, вы должны использовать @Resource вместо @Inject. Метод createOrderConnection использует QueueConnectionFactory для создания QueueConnection и дальнейшего его внедрения (попутно именуя его @Order). Если вы внимательно посмотрите на метод createOrderSession, то увидите, что его параметр — это созданный раннее для внедрения QueueConnection и с его помощью создается QueueSession. В итоге достаточно просто внедрить JMS-сессию во внешний компонент без её создания:
@Stateless
public class ItemEJB {
@Inject @Order
private QueueSession session;
@Inject @Order
private Queue orderQueue;
private void sendOrder(Book book) throws Exception {
QueueSender sender = session.createSender(orderQueue);
TextMessage message = session.createTextMessage();
message.setText(marshall(book));
sender.send(message);
}
...
}Создание и… утилизация
Вы можете сказать: "Ок, у меня есть внешний класс для черновой работы и создания соединения и сессии… но кто их будет закрывать?" Действительно, кто-то должен освободить эти ресурсы, закрыв их. И в этот момент CDI демонстрирует ещё немного магии: @Disposes.
public class JMSResourceProducer {
@Resource(name = "jms/OrderConnectionFactory")
private QueueConnectionFactory orderConnectionFactory;
@Produces @Order @Resource(name = "jms/OrderQueue")
private Queue orderQueue;
@Produces @Order
public QueueConnection createOrderConnection() throws JMSException {
return orderConnectionFactory.createQueueConnection();
}
@Produces @Order
public QueueSession createOrderSession(@Order QueueConnection conn) throws JMSException {
return conn.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
}
public void closeOrderSession(@Disposes @Order QueueConnection conn) throws JMSException {
conn.close();
}
public void closeOrderSession(@Disposes @Order QueueSession session) throws JMSException {
session.close();
}
}Для того чтобы попросить CDI закрыть ресурс, вам нужно просто определить метод, аналогичный тому, который создавал этот ресурс (createOrderSession(@Order QueueConnection conn) создаёт сессию, а closeOrderSession(@Order QueueConnection conn) для её закрытия) и добавить аннотацию @Disposes. CDI будет утилизировать ресурсы в правильном для вас порядке (сначала сессия, потом соединение). Я об этом не упоминал, но CDI создает и утилизирует ресурсы в зависимости от их области действия (resquest, session, application, conversation...). Но об этом уже в другой раз. (информация есть в Beginning Java EE 7 [перевод] — прим. пер.)
Заключение
Как вы поняли из моих предыдущих постов (часть 1, часть 2), CDI — это о внедрении зависимостей. До сих пор я показывал, как внедрять бины, но теперь вы знаете как можно внедрить что угодно (строки, примитивы, менеджеры сущностей, JMS-фабрики...) куда угодно (POJO, сервлеты, EJB...). Вам просто нужно создавать то, что вы хотите внедрять (используя либо продюсирующие поля, либо методы-продюсеры).
Исходный код
Скачайте код и расскажите, что вы о нем думаете.