Прошел год с момента публикации спецификации Java EE 7. Теперь, когда вышел Wildfly 8 Final, пришло время познакомиться с новыми возможностями.
Одна вещь, которая отсутствовала с начала дней Java EE — это возможность работать с полноценными потоками Java EE. Java EE 6 уже принесла нам аннотацию @Asynchronous, с помощью которой мы могли выполнять отдельные методы в фоновом режиме, но реальный пул потоков все еще был недоступен. Но все это теперь история, так как Java EE 7 представила ManagedExecutorService:
1
2
|
@Resource ManagedExecutorService managedExecutorService; |
Как и хорошо известный ExecutorService из Standard Edition, ManagedExecutorService можно использовать для отправки задач, которые выполняются в пуле потоков. Можно выбрать, должны ли представленные задачи реализовывать интерфейс Runnable или Callable.
В отличие от обычного экземпляра SE ExecutorService, ManagedExecutorService предоставляет потоки, которые могут получать доступ, например, к пользовательским транзакциям из JNDI для выполнения транзакций JPA во время их выполнения. Эта особенность сильно отличается от потоков, запускаемых как в среде SE.
Важно знать, что транзакции, запущенные в пуле потоков ManagedExecutorService, выполняются вне области транзакции потока, который отправляет задачи. Это позволяет реализовать сценарии, в которых подающий поток вставляет некоторую информацию о запущенных задачах в базу данных, в то время как долгосрочные задачи выполняют свою работу в рамках независимой транзакции.
Теперь, после того, как мы узнали что-то о теории, давайте возьмемся за некоторый код. Сначала мы пишем @Stateless EJB, который получает ManagedExecutorService:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
@Stateless public class MyBean { @Resource ManagedExecutorService managedExecutorService; @PersistenceContext EntityManager entityManager; @Inject Instance<MyTask> myTaskInstance; public void executeAsync() throws ExecutionException, InterruptedException { for ( int i= 0 ; i< 10 ; i++) { MyTask myTask = myTaskInstance.get(); this .managedExecutorService.submit(myTask); } } public List<MyEntity> list() { return entityManager.createQuery( "select m from MyEntity m" , MyEntity. class ).getResultList(); } } |
Задачи, которые мы передадим в ManagedExecutorService, извлекаются из механизма экземпляров CDI. Это позволяет нам использовать возможности CDI в нашем классе MyTask:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public class MyTask implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(MyTask. class ); @PersistenceContext EntityManager entityManager; @Override public void run() { UserTransaction userTransaction = null ; try { userTransaction = lookup(); userTransaction.begin(); MyEntity myEntity = new MyEntity(); myEntity.setName( "name" ); entityManager.persist(myEntity); userTransaction.commit(); } catch (Exception e) { try { if (userTransaction != null ) { userTransaction.rollback(); } } catch (SystemException e1) { LOGGER.error( "Failed to rollback transaction: " +e1.getMessage()); } } } private UserTransaction lookup() throws NamingException { InitialContext ic = new InitialContext(); return (UserTransaction)ic.lookup( "java:comp/UserTransaction" ); } } |
Здесь мы можем добавить EntityManager для сохранения некоторых объектов в нашей базе данных. UserTransaction, который нам нужен для фиксации, должен быть получен из JNDI. Инъекция с использованием аннотации @Resource невозможна в пределах обычного управляемого компонента.
Чтобы обойти UserTransaction, мы, конечно, могли бы вызвать метод другого EJB и использовать транзакцию другого EJB для фиксации изменений в базе данных. В следующем коде показана альтернативная реализация, использующая вставленный EJB для сохранения сущности:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public class MyTask implements Runnable { private static final Logger LOGGER = LoggerFactory.getLogger(MyTask. class ); @PersistenceContext EntityManager entityManager; @Inject MyBean myBean; @Override public void run() { MyEntity myEntity = new MyEntity(); myBean.persit(myEntity); } } |
Теперь нам нужно только использовать JAX-RS для вызова функциональности через интерфейс REST:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
@Path ( "/myResource" ) public class MyResource { @Inject private MyBean myBean; @Path ( "list" ) @GET @Produces ( "text/json" ) public List<MyEntity> list() { return myBean.list(); } @Path ( "persist" ) @GET @Produces ( "text/html" ) public String persist() throws ExecutionException, InterruptedException { myBean.executeAsync(); return "<html><h1>Successful!</h1></html>" ; } } |
Вот и все. С помощью этих нескольких строк кода мы реализовали полностью работающее приложение Java EE, функциональность которого можно вызывать через интерфейс REST и которое выполняет свои основные функции асинхронно в рабочих потоках со своими собственными транзакциями.
Вывод
ManagedExecutorService — отличная функция для интеграции асинхронных функций с использованием всех стандартных функций Java EE, таких как JPA и транзакций, в корпоративные приложения. Я бы сказал, что ожидание того стоило.
- Пример исходного кода можно найти на github .