@WebServlet(urlPatterns={"/dukeetf"}, asyncSupported=true)
public class DukeETFServlet extends HttpServlet {
...
}
Java Platform, Enterprise Edition (Java EE) 8 Учебник по Java EE |
Назад | Вперёд | Содержание |
Пример приложения dukeetf
, расположенного в каталоге tut-install/examples/web/dukeetf/
, демонстрирует, как использовать асинхронную обработку в сервлете для предоставления обновлённых данных веб-клиентам. Пример напоминает сервис, который предоставляет периодические обновления цены и объёма торгов биржевым инвестиционным фондом (ETF).
Здесь рассматриваются следующие темы:
Приложение dukeetf
состоит из сервлета, Enterprise-бина и страницы HTML.
Сервлет помещает запросы в асинхронном режиме, сохраняет их в очереди и записывает ответы, когда становятся доступными новые данные о цене и объёме торгов.
Enterprise-бин обновляет информацию о цене и объёме каждую секунду.
HTML-страница использует код JavaScript, чтобы отправлять сервлету запросы на новые данные, анализировать ответ сервлета и обновлять информацию о цене и объёме без перезагрузки страницы.
В примере приложения dukeetf
используется программная модель, известная как long polling. В традиционной модели HTTP-запросов и ответов пользователь должен сделать явный запрос (например, кликнуть ссылку или отправить форму), чтобы получить любую новую информацию с сервера, и страница должна быть перезагружена. Long polling предоставляет веб-приложениям механизм для отправки обновлений клиентам, использующим HTTP, без явного запроса пользователя. Сервер обрабатывает соединения асинхронно, а клиент использует JavaScript для создания новых соединений. В этой модели клиенты делают новый запрос сразу после получения новых данных, и сервер сохраняет соединение открытым, пока новые данные не станут доступны.
Класс DukeETFServlet
использует асинхронную обработку:
@WebServlet(urlPatterns={"/dukeetf"}, asyncSupported=true)
public class DukeETFServlet extends HttpServlet {
...
}
В следующем коде метод init
инициализирует очередь для хранения клиентских запросов и регистрирует сервлет с Enterprise-бином, который предоставляет обновления цены и объёма. Раз в секунду вызывается метод send
у PriceVolumeBean
для отправки обновлений и закрытия соединения:
@Override
public void init(ServletConfig config) {
/* Очередь запросов */
requestQueue = new ConcurrentLinkedQueue<>();
/* Регистрация в EJB, который предоставляет обновления цены и объёма */
pvbean.registerServlet(this);
}
/* PriceVolumeBean вызывает этот метод каждую секунду для отправки обновлений */
public void send(double price, int volume) {
/* Отправка обновлений всем подключенным клиентам */
for (AsyncContext acontext : requestQueue) {
try {
String msg = String.format("%.2f / %d", price, volume);
PrintWriter writer = acontext.getResponse().getWriter();
writer.write(msg);
logger.log(Level.INFO, "Sent: {0}", msg);
/* Закрытие соединения
* Клиент (JavaScript) постоянно открывает новое */
acontext.complete();
} catch (IOException ex) {
logger.log(Level.INFO, ex.toString());
}
}
}
Сервисный метод переводит клиентские запросы в асинхронный режим и добавляет слушатель к каждому запросу. Слушатель реализован как анонимный класс, который удаляет запрос из очереди, когда сервлет заканчивает писать ответ или когда возникает ошибка. Наконец, метод service добавляет запрос в очередь запросов, созданную в методе init
. Сервисный метод описан следующим образом:
@Override
public void doGet(HttpServletRequest request,
HttpServletResponse response) {
response.setContentType("text/html");
/* Переключение запроса в асинхронный режим */
final AsyncContext acontext = request.startAsync();
/* Удаление из очереди после выполнения */
acontext.addListener(new AsyncListener() {
public void onComplete(AsyncEvent ae) throws IOException {
requestQueue.remove(acontext);
}
public void onTimeout(AsyncEvent ae) throws IOException {
requestQueue.remove(acontext);
}
public void onError(AsyncEvent ae) throws IOException {
requestQueue.remove(acontext);
}
public void onStartAsync(AsyncEvent ae) throws IOException {}
});
/* Добавление в очередь */
requestQueue.add(acontext);
}
Класс PriceVolumeBean
является Enterprise-бином, который использует сервис таймера из контейнера для обновления информации о цене и объёме и вызывает метод send
сервлета раз в секунду:
@Startup
@Singleton
public class PriceVolumeBean {
/* Использование сервиса таймера контейнера */
@Resource TimerService tservice;
private DukeETFServlet servlet;
...
@PostConstruct
public void init() {
/* Инициализация EJB и создание таймера */
random = new Random();
servlet = null;
tservice.createIntervalTimer(1000, 1000, new TimerConfig());
}
public void registerServlet(DukeETFServlet servlet) {
/* Сервлет для отправки обновлений */
this.servlet = servlet;
}
@Timeout
public void timeout() {
/* Настройка цены и объёма и отправка обновления */
price += 1.0*(random.nextInt(100)-50)/100.0;
volume += random.nextInt(5000) - 2500;
if (servlet != null)
servlet.send(price, volume);
}
}
Смотрите Использование сервиса таймера в главе 37 «Запуск примеров Enterprise-бина» для получения дополнительной информации о сервисе таймера.
HTML-страница состоит из таблицы и кода JavaScript. Таблица содержит два поля, на которые ссылается код JavaScript:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>...</head>
<body onload="makeAjaxRequest();">
...
<table>
...
<td id="price">--.--</td>
...
<td id="volume">--</td>
...
</table>
</body>
</html>
Код JavaScript использует API XMLHttpRequest
, который обеспечивает функциональность для передачи данных между клиентом и сервером. Скрипт выполняет асинхронный запрос к сервлету и назначает Callback-метод. Когда сервер предоставляет ответ, Callback-метод обновляет поля в таблице и создаёт новый запрос. Код JavaScript выглядит следующим образом:
var ajaxRequest;
function updatePage() {
if (ajaxRequest.readyState === 4) {
var arraypv = ajaxRequest.responseText.split("/");
document.getElementById("price").innerHTML = arraypv[0];
document.getElementById("volume").innerHTML = arraypv[1];
makeAjaxRequest();
}
}
function makeAjaxRequest() {
ajaxRequest = new XMLHttpRequest();
ajaxRequest.onreadystatechange = updatePage;
ajaxRequest.open("GET", "http://localhost:8080/dukeetf/dukeetf",
true);
ajaxRequest.send(null);
}
API XMLHttpRequest
поддерживается большинством современных браузеров и широко используется при разработке веб-клиента Ajax (асинхронный JavaScript и XML).
Смотрите Приложение dukeetf2 в главе 19 «Java API для веб-сокетов» для эквивалентной версии этого примера, реализованной с использованием конечной точки веб-сокета.
В этом разделе описывается, как запустить пример dukeetf
в IDE NetBeans и из командной строки.
Здесь рассматриваются следующие темы:
Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).
В меню «Файл» выберите «Открыть проект».
В диалоговом окне «Открыть проект» перейдите к:
tut-install/examples/web/servlet
Выберите каталог dukeetf
.
Нажмите Открыть проект.
На вкладке «Проекты» кликните правой кнопкой мыши проект dukeetf
и выберите «Выполнить».
Эта команда собирает и упаковывает приложение в WAR-файл (dukeetf.war
), расположенный в каталоге target
, развёртывает его на сервере и запускает окно веб-браузера со следующим URL:
http://localhost:8080/dukeetf/
Откройте этот же URL в другом веб-браузере, чтобы увидеть, как обе страницы одновременно получают обновления цены и объёма.
Удостоверьтесь, чтобы GlassFish Server был запущен (см. Запуск и остановка сервера GlassFish).
В окне терминала перейдите в:
tut-install/examples/web/servlet/dukeetf/
Введите следующую команду для развёртывания приложения:
mvn install
Откройте окно веб-браузера и введите следующий URL:
http://localhost:8080/dukeetf/
Откройте этот же URL в другом веб-браузере, чтобы увидеть, как обе страницы одновременно получают обновления цены и объёма.
Назад | Вперёд | Содержание |