Впровадження залежностей в CDI. Частина 3

Якщо ви стежите за цим блогом, то пам'ятайте, що останнім часом я пишу (і говорю) про CDI (Contexts and Dependency Injection). У CDI багато аспектів, але досі я акцентував увагу на тому, як почати роботу з CDI у вашому оточенні і як інтегрувати CDI в існуюче Java EE 6 додаток, а потім сфокусувався на впровадженні залежностей в CDI. Це вже третій пост про впровадження в CDI: першому я розповідав про впровадження за замовчуванням і спецификаторах, у другий про всіх можливих точках впровадження поле, конструктор, сетер). У цьому пості я розповім про продюсерів "ви можете типобезопасным способом впроваджувати що завгодно і куди завгодно".
COFFEE_BEANS
Впроваджуємо тільки бины?
До цього я показував вам, як впроваджувати бины за допомогою звичайної анотації
@Inject
. Якщо ви подивіться на приклад з генерацією номера книги, який я використовував раніше, то побачите сервлет і rest сервіс, що впроваджується реалізація інтерфейсу
NumberGenerator
. Завдяки спецификаторам, сервлет може отримати
IsbnGenerator
, позначений в точці впровадження специфікатором
@ThirteenDigit
, а rest сервіс отримає
IssnGenerator
, позначений
@EightDigits
(дивіться мій перший пост). Представлена діаграма класів демонструє деяку комбінацію впровадження бинов:
CDI_3_1
Але як ви бачите, все це впровадження бинов в інші бины. У 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
, які будуть використовуватися для генерації номери:
CDI_3_2
Якщо ми подивимося, наприклад, на код
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...). Вам просто потрібно створювати те, що ви хочете впроваджувати (використовуючи або продюсирующие поля, або методи-продюсери).
Вихідний код
Скачайте код і розкажіть, що ви про нього думаєте.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.