До сих пор вы видели, что можете преобразовать любой метод компонента в асинхронный метод. Теперь вы увидите, как заставить асинхронно функционировать
сервлет. Без наличия асинхронной поддержки в сервлетах нелегко отвечать требованиям асинхронности при веб-разработке.
Спецификация Servlet 3.0 (JSR 315) внесла серьезные усовершенствования в интерфейсы программирования веб-приложений (API) языка Jаvа. С появлением JSR 315 спецификации сервлетов были обновлены (после длительного ожидания) для поддержки асинхронной модели выполнения, удобной конфигурации, подключаемости и других мелких улучшений.
Асинхронные сервлеты основываются на ключевом усовершенствовании в HyperText Traпsfer Protocol (НТТР) 1.1, сделавшем возможными постоянные соединения. В НТТР 1.0 каждое соединение использовалось для отправки и получения только одной пары «запрос/ответ«; в то же время НТТР 1.1 позволяет веб-приложениям поддерживать соединение в активном состоянии и посылать множественные запросы.
При стандартной реализации прикладная часть Java потребовала бы отдельного потока, постоянно прикрепленного к НТТР-соединению. Однако неблокирующее API ввода-вывода языка Java (Java Nonblocking I/O, NIO) возвращает между активными запросами потоки «в оборот» благодаря новым возможностям NIO. На сегодняшний день все совместимые со спецификациями Servlet 3.0 веб серверы имеют встроенную поддержку Java NIO.
Для чего вам может понадобиться от сервлетов подобное поведение? Для серверных систем характерны длительные операции, такие как соединение с другими серверами, выполнение сложных вычислений и осуществление операций транзакционных баз данных. Однако сущность веб-страниц требует как раз обратного.
Веб-пользователи ожидают короткого времени отклика и UI, функционирующего даже в случае незавершенных операций серверной части. AJАХ взял на себя решение этой проблемы для браузеров и начал революцию Web 2.0.
Спецификация Servlet 3.0 предоставила метод startAsync(), сделавший доступными асинхронные операции. Следующий пример кода показывает пример этого:
1 2 3 4 5 6 7 8 9 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 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
// Пример использования метода startAsync() package com.devchronicles.asynchronous; import java.io.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; @WebServlet(urlPatterns={"/async"}, asyncSupported=true) public class AsyncServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException { final AsyncContext asyncContext = req.startAsync(); final String data; asyncContext.addListened(new AsyncListener() { @Override public void onComplete(AsyncEvent event) throws IOException { AsyncContext asyncContext = event.getAsyncContext(); asyncContext().getWriter().println(data); } @Override public void onTimeout(AsyncEvent event) throws IOException { // Код опущен для краткости } @Override public void onError(AsyncEvent event) throws IOException { // Код опущен для краткости } @Override public void onStartAsync(AsyncEvent event) throws IOException { // Код опущен для краткости } }); new Thread() { @Override public void run() { asyncContext.complete(); } }.start(); res.getWriter().write("Results:"); // Чтение данных из базы данных data = "Queried data..."; // Переводим поток в режим ожидания на некоторое время... } } |
Сервлет выводит Results: и далее выводит полученные из базы данные, которыми в этом сценарии является простая строка. Вам необходимо инициализировать отдельный поток.
Метод onComplete класса Asynclistener выполняется только после завершения выполнения. В классе Asynclistener есть еще несколько методов жизненного цикла.
- OnStartAsync — выполняется при запуске асинхронного контекста;
- OnTimeOut — выполняется, только если истекает время ожидания;
- onError — выполняется, только если была получена ошибка.
Спецификация Servlet 3.1 предоставляет более простой способ реализации асинхронных сервлетов путем использования управляемых пулов потоков и сервиса выполнения.
В следующем примере задействуется ManagedThreadFactory для создания нового потока.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// Пример использующий ManagedThreadFactory package com.devchronicles.asynchronous; import java.io.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; @WebServlet(urlPatterns="/async", asyncSupported=true) public class AsyncServlet extends HttpServlet { @Resource private ManagedThreadFactory factory; @Overrid protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { final AsyncContext asyncContext = req.startAsync(); final PrintWriter writer = res.getWriter(); Thread thread = factory.newThread(new Runnable() { @Override public void run() { writer.println("Complete!"); asyncContext.complete(); } }); thread.start(); } } |
Этот пример создает новый поток с процессом, требующим больших затрат времени, и наконец вызывает функцию complete из asyncContext. ManagedThreadFactory служит в качестве доступного потока из пула, который вам необходимо запустить явным образом.
Другой подход состоит в передаче ManagedExecutorServiсе асинхронного Runnable вместо создания и последующего запуска потока в сервлете. Делегирование ExecutorServiсе вопросов организации поточной обработки обеспечивает более «чистый» код, как вы увидите в следующем примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// Пример делегирования ExecutorService package com.devchronicles.asynchronous; import java.io.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; @WebServlet(urlPatterns="/async", asyncSupported=true) public class AsyncServlet extends HttpServlet { @Resource private ManagedExecutorService executor; @Override protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { final AsyncContext asyncContext = req.startAsync(); final PrintWriter writer = res.getWriter(); executor.submit(new Runnable() { @Override public void run() { writer.println("Complete!"); asyncContext.complete(); } }); } |
Хотя это всего лишь на одну строку меньше, чем в предыдущем примере, программа делегирует создание и запуск потока ExecutorServiсе и имеет дело только с сервлетным кодом.
Асинхронные сервлеты легче для понимания и программирования и оказывают немедленный эффект на динамическое поведение, поскольку напрямую «переключают» на модель асинхронного выполнения. Асинхронные сервлеты обеспечивают «чистую» реализацию без большого количества шаблонного кода.