Статьи

Java EE: асинхронные конструкции и возможности

асинхронный

Вступление

Java EE имеет ряд API и конструкций для поддержки асинхронного выполнения. Это жизненно важно с точки зрения масштабируемости и производительности.

Допустим, 2 модуля взаимодействуют друг с другом. Когда moduleA (отправитель) отправляет сообщение в moduleB (получатель) в синхронном режиме, связь происходит в контексте одного потока, то есть поток, который инициировал связь с moduleA, блокируется до тех пор, пока moduleB не ответит обратно.

Это было универсальное утверждение, но его можно распространить на контекст простых Java-методов, взаимодействующих друг с другом — в этом случае синхронный вызов от methodA к methodB будет выполняться в том же потоке, который будет заблокирован, пока methodB не вернет или не сгенерирует исключение ,

Зачем нам нужно асинхронное поведение в приложениях на основе Java EE?

Мы можем дополнительно экстраполировать эту точку на мир Java EE — будь то межсерверное взаимодействие, например, между веб-уровнем и уровнем EJB (сервлетами и EJB), или типичное взаимодействие клиент-сервер — браузер, взаимодействующий с конечной точкой RESTful, сервлетами и т. Д. — сервер поток, который отвечает на запрос клиента, всегда блокируется, пока компонент сервера не ответит обратно.

Вот где в игру вступает асинхронное выполнение — если поток сервера, обрабатывающий клиентский запрос, может быть освобожден / приостановлен, а фактическая бизнес-логика выполняется в отдельном потоке (отличном от исходного), производительность и масштабируемость могут быть значительно улучшены ! Например, если поток прослушивателя HTTP, выделенный для прослушивания клиентских запросов, освобождается немедленно, тогда он может выполнять запросы других клиентов, и бизнес-логика может выполняться в отдельном потоке контейнера, который затем может возвращать ответ с помощью соответствующих методов, таких как java.util.concurrent.Future объект или через обработчики обратного вызова, зарегистрированные клиентом. Подумайте с точки зрения конечного пользователя — отзывчивость имеет большое значение!

Погружение: асинхронные конструкции и API в Java EE

Давайте рассмотрим некоторые функции, связанные с Async (API) в Java EE. Это не исчерпывающий список, но он должен стать хорошей отправной точкой.

Различные спецификации Java EE имеют свои типичные способы и API для облегчения возможностей асинхронного подключения. Давайте рассмотрим следующие спецификации Java EE

  • JAX-RS 2.0 (Java EE 7)
  • Websocket 1.0 (Java EE 7)
  • Утилиты параллелизма 1.0 (Java EE 7)
  • EJB 3.1 (Java EE 6)
  • Сервлет 3.0 (Java EE 6)

Примечание . Код, представленный ниже, представлен в виде фрагмента кода (по понятным причинам). С полными образцами можно ознакомиться здесь .

JAX-RS 2.0

Асинхронная обработка запросов — это новая функция в версии 2.0 JAX-RS (новая в Java EE 7). Чтобы выполнить запрос aysnc с использованием API-интерфейсов JAX-RS, необходимо внедрить ссылку на интерфейс javax.ws.rs.container.AsyncResponse в самом методе ресурсов JAX-RS. Этот параметр переводит выполнение запроса в асинхронный режим, и метод продолжает его выполнение. Метод возобновления объекта AsynResponse должен вызываться из отдельного потока после завершения выполнения бизнес-логики. Можно использовать функции утилиты параллелизма Java EE (обсуждаемые позже), такие как javax.enterprise.concurrent.ManagedExecutorService , чтобы инкапсулировать бизнес-логику в виде объекта Runnable и передать ее службе выполнения контейнера, которая позаботится обо всем остальном. Нет необходимости создавать неуправляемые изолированные потоки самостоятельно.

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
@Path("/{id}")
    @GET
    @Produces("application/xml")
    public void asyncMethod(@Suspended AsyncResponse resp, @PathParam("id") String input) {
 
        System.out.println("Entered MyAsyncRESTResource/asyncMethod() executing in thread: "+ Thread.currentThread().getName());
        mes.execute(
                () -> {
                    System.out.println("Entered Async zone executing in thread: "+ Thread.currentThread().getName());
                    System.out.println("Simulating long running op via Thread sleep() started on "+ new Date().toString());
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(MyAsyncRESTResource.class.getName()).log(Level.SEVERE, null, ex);
                    }
                    System.out.println("Completed Long running op on "+new Date().toString());
                    System.out.println("Exiting Async zone executing in thread: "+ Thread.currentThread().getName());
 
                    //creating a dummy instance of our model class (Student)
 
                    Student stud = new Student(input, "Abhishek", "Apr-08-1987");
                    resp.resume(Response.ok(stud).build());
                }
        );
 
        System.out.println("Exit MyAsyncRESTResource/asyncMethod() and returned thread "+Thread.currentThread().getName()+" back to thread pool");
    }

Клиентский API JAX-RS также имеет асинхронные возможности, но они не обсуждались в посте. На них определенно стоит посмотреть!

Websocket 1.0

API Websocket является совершенно новым дополнением к арсеналу Java EE (представлен в Java EE 7). Это облегчает двунаправленную (инициированную как сервером, так и клиентом) связь, которая также является дуплексной по своему характеру (клиент или сервер могут отправлять сообщения друг другу в любое время).

Для отправки асинхронных сообщений с помощью API Websocket необходимо использовать метод getAsyncRemote, доступный в интерфейсе javax.websocket.Session . Внутренне это не что иное, как экземпляр вложенного интерфейса javax.websocket.RemoteEnpointjavax.websocket.RemoteEnpoint.Async . Вызов обычных методов sendXXX для этого приведет к тому, что процесс отправки будет выполняться в отдельном потоке. Это особенно полезно, когда вы рассматриваете обмен большими сообщениями или обработку большого количества клиентов веб-сокетов, которым сообщения должны быть отправлены. Метод wither возвращает объект java.util.concurrent.Future или можно зарегистрировать обратный вызов в форме реализации интерфейса javax.websocket.SendHandler .

01
02
03
04
05
06
07
08
09
10
11
12
13
public void sendMsg(@Observes Stock stock) {
        System.out.println("Message receieved by MessageObserver --> "+ stock);
        System.out.println("peers.size() --> "+ peers.size());
        peers.stream().forEach((aPeer) -> {
            //stock.setPrice();
 
                            aPeer.getAsyncRemote().sendText(stock.toString(), (result) -> {
                System.out.println("Message Sent? " + result.isOK());
                System.out.println("Thread : " + Thread.currentThread().getName());
            });
 
        });
    }

Параллельные утилиты 1.0

Утилиты для параллелизма Java EE — еще одно замечательное дополнение к Java EE 7 . Он предоставляет стандартный способ порождения потоков — хорошая его часть заключается в том, что они управляются контейнером, а не просто изолированными / потерянными потоками, о которых контейнер не имеет контекстной информации.

В общем, Concurrency Utilities 1.0 предоставляют несколько стандартных конструкций для выполнения асинхронных задач в отдельных потоках. Это следующие: javax.enterprise.concurrent.ManagedExecutorService и javax.enterprise.concurrent.ManagedScheduledExecutorService .

Чтобы запустить новую задачу в отдельном потоке, можно использовать интерфейс ManagedExecutorService для отправки Runnable . В дополнение к реализации интерфейса Runnable класс также может реализовать интерфейс javax.enterprise.concurrent.ManagedTask и предоставить реализацию javax.enterprise.concurrent.ManagedTaskListener для прослушивания изменений жизненного цикла в задаче, переданной через ManagedExecutorService.

1
2
3
4
5
6
7
8
9
@Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
 
        System.out.println("Enter AConcurrencyUtilsExample/doGet executing in thread "+ Thread.currentThread().getName());
        System.out.println("initiating task . . . ");
        mes.execute(new AManagedTask());
        System.out.println("Exit AConcurrencyUtilsExample/doGet and returning thread "+ Thread.currentThread().getName() +" back to pool");
    }

Сервлет 3.0

Асинхронный HTTP был введен в Servlet 3.0 (часть Java EE 6 ), который в основном предоставлял возможность выполнить запрос в отдельном потоке и приостановить исходный поток, который обрабатывал вызов клиента.

Ключевым игроком здесь является интерфейс javax.servlet.AsyncContext . Чтобы инициировать асинхронную обработку, вызывается метод startAsync интерфейса java.servlet.ServletRequest . Чтобы выполнить основную логику, объект java.lang.Runnable должен быть передан методу запуска интерфейса AsyncContext. Можно подключить прослушиватель, реализовав javax.servlet.AsyncListener для получения уведомлений об обратном вызове в течение определенного времени выполнения задачи Async.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Override
 
    public void doGet(HttpServletRequest req, HttpServletResponse resp) {
 
        PrintWriter writer = null;
        try {
            writer = resp.getWriter();
        } catch (IOException ex) {
            Logger.getLogger(MyAsyncServlet.class.getName()).log(Level.SEVERE, null, ex);
        }
        //System.out.println("entered doGet()");
        writer.println("ENTERING ... " + MyAsyncServlet.class.getSimpleName() + "/doGet()");
        writer.println("Executing in Thread: " + Thread.currentThread().getName());
        //step 1
        final AsyncContext asyncContext = req.startAsync();
 
        //step 2
        asyncContext.addListener(new CustomAsyncHandler(asyncContext));
 
        //step 3
        asyncContext.start(
                () -> {
                    PrintWriter logger = null;
                    try {
                        logger = asyncContext.getResponse().getWriter();
                    } catch (IOException ex) {
                        Logger.getLogger(MyAsyncServlet.class.getName()).log(Level.SEVERE, null, ex);
                    }
 
                    logger.println("Long running Aync task execution started : " + new Date().toString());
 
                    logger.println("Executing in Thread: " + Thread.currentThread().getName());
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        Logger.getLogger(MyAsyncServlet.class.getName()).log(Level.SEVERE, null, e);
                    }
 
                    logger.println("Long task execution complete : " + new Date().toString());
 
                    logger.println("Calling complete() on AsyncContext");
 
                    //step 4
                    asyncContext.complete();
                }
        );
 
        writer.println("EXITING ... " + MyAsyncServlet.class.getSimpleName() + "/doGet() and returning initial thread back to the thread pool");
 
    }

EJB 3.1

Как правило, (до EJB 3.1) EJB Message Driven Beans использовались для выполнения асинхронных требований. Компонент MDB прослушивает сообщения, отправленные в javax.jms.Destination ( очередь или тема ), и выполняет требуемую бизнес-логику — это может быть что угодно, от отправки сообщения электронной почты до инициирования задачи обработки заказа. Важно понимать, что клиент, который отправляет сообщение в очередь, в первую очередь, не знает о MDB ( отделен ) и не должен ждать / оставаться заблокированным до конца задания (получение по электронной почте или подтверждение обработки заказа). ).

В EJB 3.1 (часть Java EE 6 ) появилась аннотация javax.ejb.Asynchronous . Это может быть помещено в класс EJB Session bean (Stateless, Stateful или Singleton) (делает все методы асинхронными) или на самом уровне метода — в случае, если требуется детальное управление. Метод с аннотацией @Asynchronous может либо возвращать void (запустить и забыть), либо экземпляр java.util.concurrent.Future, если необходимо отслеживать результат асинхронного метода — это можно сделать, вызвав метод Future.get () — Следует отметить, что сам метод get является блокирующим по своей природе.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
@Asynchronous
        public Future<String> asyncEJB2(){
 
        System.out.println("Entered MyAsyncEJB/asyncEJB2()");
       System.out.println("MyAsyncEJB/asyncEJB2() Executing in thread: "+ Thread.currentThread().getName());
        System.out.println("Pretending as if MyAsyncEJB/asyncEJB2() is doing something !");
        try {
            Thread.sleep(5000);
        } catch (InterruptedException ex) {
            java.util.logging.Logger.getLogger(MyAsyncEJB.class.getName()).log(Level.SEVERE, null, ex);
        }
 
        System.out.println("Exiting MyAsyncEJB/asyncEJB2()");
        return new AsyncResult("Finished Executing on "+ new Date().toString());
 
    }

Это был довольно краткий обзор возможностей Java EE. Эти API-интерфейсы и спецификации функционально богаты, и их все сложно описать в блоге! Я надеюсь, что это заинтересует вас и даст вам отправную точку для дальнейшего изучения.

Ура!