Автор оригинала: Pankaj Kumar.
Асинхронный сервлет был представлен в сервлете 3. Это отличный способ справиться с проблемой нехватки потоков при длительных потоках.
Асинхронный Сервлет
Прежде чем мы перейдем к пониманию того, что такое асинхронный сервлет, давайте попробуем понять, зачем он нам нужен.
Допустим, у нас есть сервлет, обработка которого занимает много времени, как показано ниже.
package com.journaldev.servlet;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/LongRunningServlet")
public class LongRunningServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
System.out.println("LongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId());
String time = request.getParameter("time");
int secs = Integer.valueOf(time);
// max 10 seconds
if (secs > 10000)
secs = 10000;
longProcessing(secs);
PrintWriter out = response.getWriter();
long endTime = System.currentTimeMillis();
out.write("Processing done for " + secs + " milliseconds!!");
System.out.println("LongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
private void longProcessing(int secs) {
// wait for given time before finishing
try {
Thread.sleep(secs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Если мы нажмем выше сервлета через браузер с URL как https://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000 , мы получаем ответ “Обработка выполнена за 8000 миллисекунд!!” через 8 секунд. Теперь, если вы заглянете в журналы сервера, вы получите следующий журнал:
LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103 LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103::Time Taken=8002 ms.
Таким образом, наш поток сервлетов выполнялся в течение ~8+ секунд, хотя большая часть обработки не имеет ничего общего с запросом или ответом сервлета.
Это может привести к Голоданию потока – так как наш поток сервлета заблокирован до тех пор, пока не будет выполнена вся обработка. Если сервер получит много запросов для обработки, он достигнет максимального предела потока сервлета, и дальнейшие запросы будут получать ошибки отказа в подключении.
До Servlet 3.0 для этих длительных потоков существовало специальное контейнерное решение, в котором мы могли создавать отдельный рабочий поток для выполнения тяжелой задачи, а затем возвращать ответ клиенту. Поток сервлета возвращается в пул сервлетов после запуска рабочего потока. Комета Tomcat, Weblogic FutureResponseServlet и Диспетчер асинхронных запросов Websphere являются некоторыми примерами реализации асинхронной обработки.
Проблема с решением для конкретного контейнера заключается в том, что мы не можем перейти в другой контейнер сервлетов без изменения кода нашего приложения, поэтому в Servlet 3.0 была добавлена поддержка асинхронных сервлетов, чтобы обеспечить стандартный способ асинхронной обработки в сервлетах.
Асинхронная Реализация Сервлета
Давайте рассмотрим шаги по реализации асинхронного сервлета, а затем мы предоставим асинхронный поддерживаемый сервлет для приведенного выше примера.
- Прежде всего, сервлет, в котором мы хотим обеспечить поддержку асинхронности, должен иметь @WebServlet | аннотацию с поддержкой ASYNC значением true . Поскольку фактическая работа должна быть делегирована другому потоку, у нас должна быть реализация пула потоков. Мы можем создать
- пул потоков с помощью платформы исполнителей и использовать servletcontextlistener для инициализации пула потоков. Нам нужно получить экземпляр
- AsyncContext через ServletRequest.startAsync()
метод. AsyncContext предоставляет методы для получения ссылок на объекты ServletRequest и ServletResponse. Он также предоставляет метод для пересылки запроса на другой ресурс с использованием метода dispatch ().У нас должна быть - Запускаемая реализация , в которой мы будем выполнять тяжелую обработку, а затем использовать объект AsyncContext либо для отправки запроса на другой ресурс, либо для записи ответа с использованием объекта ServletResponse. Как только обработка будет завершена, мы должны вызвать метод AsyncContext.complete (), чтобы сообщить контейнеру, что асинхронная обработка завершена. Мы можем добавить реализацию AsyncListener в объект AsyncContext для реализации методов обратного вызова – мы можем использовать это для предоставления ответа на ошибку клиенту в случае ошибки или тайм-аута во время обработки асинхронного потока. Мы также можем провести здесь кое-какие работы по очистке.
Как только мы завершим наш проект для примера асинхронного сервлета, он будет выглядеть следующим образом.
Инициализация пула рабочих потоков в Прослушивателе контекста Сервлета
package com.journaldev.servlet.async;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppContextListener implements ServletContextListener {
public void contextInitialized(ServletContextEvent servletContextEvent) {
// create the thread pool
ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue(100));
servletContextEvent.getServletContext().setAttribute("executor",
executor);
}
public void contextDestroyed(ServletContextEvent servletContextEvent) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent
.getServletContext().getAttribute("executor");
executor.shutdown();
}
}
Реализация довольно проста, если вы не знакомы с фреймворком исполнителей, пожалуйста, прочтите раздел Исполнитель пула потоков .
Для получения более подробной информации о прослушивателях, пожалуйста, ознакомьтесь с примером прослушивателя сервлета .
Реализация Рабочего потока
package com.journaldev.servlet.async;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
public class AsyncRequestProcessor implements Runnable {
private AsyncContext asyncContext;
private int secs;
public AsyncRequestProcessor() {
}
public AsyncRequestProcessor(AsyncContext asyncCtx, int secs) {
this.asyncContext = asyncCtx;
this.secs = secs;
}
@Override
public void run() {
System.out.println("Async Supported? "
+ asyncContext.getRequest().isAsyncSupported());
longProcessing(secs);
try {
PrintWriter out = asyncContext.getResponse().getWriter();
out.write("Processing done for " + secs + " milliseconds!!");
} catch (IOException e) {
e.printStackTrace();
}
//complete the processing
asyncContext.complete();
}
private void longProcessing(int secs) {
// wait for given time before finishing
try {
Thread.sleep(secs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Обратите внимание на использование AsyncContext и его использование для получения объектов запроса и ответа, а затем завершения асинхронной обработки с помощью вызова метода complete ().
Реализация AsyncListener
package com.journaldev.servlet.async;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebListener;
@WebListener
public class AppAsyncListener implements AsyncListener {
@Override
public void onComplete(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onComplete");
// we can do resource cleanup activity here
}
@Override
public void onError(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onError");
//we can return error response to client
}
@Override
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onStartAsync");
//we can log the event here
}
@Override
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
System.out.println("AppAsyncListener onTimeout");
//we can send appropriate response to client
ServletResponse response = asyncEvent.getAsyncContext().getResponse();
PrintWriter out = response.getWriter();
out.write("TimeOut Error in Processing");
}
}
Обратите внимание на реализацию метода onTimeout () , в котором мы отправляем клиенту ответ на тайм-аут.
Пример реализации асинхронного Сервлета
Вот реализация нашего асинхронного сервлета, обратите внимание на использование AsyncContext и ThreadPoolExecutor для обработки.
package com.journaldev.servlet.async;
import java.io.IOException;
import java.util.concurrent.ThreadPoolExecutor;
import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet(urlPatterns = "/AsyncLongRunningServlet", asyncSupported = true)
public class AsyncLongRunningServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet Start::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId());
request.setAttribute("org.apache.catalina.ASYNC_SUPPORTED", true);
String time = request.getParameter("time");
int secs = Integer.valueOf(time);
// max 10 seconds
if (secs > 10000)
secs = 10000;
AsyncContext asyncCtx = request.startAsync();
asyncCtx.addListener(new AppAsyncListener());
asyncCtx.setTimeout(9000);
ThreadPoolExecutor executor = (ThreadPoolExecutor) request
.getServletContext().getAttribute("executor");
executor.execute(new AsyncRequestProcessor(asyncCtx, secs));
long endTime = System.currentTimeMillis();
System.out.println("AsyncLongRunningServlet End::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
}
Запуск асинхронного веб-приложения Сервлета
Теперь, когда мы запустим вышеуказанный сервлет с URL-адресом как https://localhost:8080/AsyncServletExample/AsyncLongRunningServlet?time=8000 мы получаем тот же ответ и журналы, что и:
AsyncLongRunningServlet Start::Name=http-bio-8080-exec-50::ID=124 AsyncLongRunningServlet End::Name=http-bio-8080-exec-50::ID=124::Time Taken=1 ms. Async Supported? true AppAsyncListener onComplete
Если мы запускаемся со временем 9999, наступает тайм-аут, и мы получаем ответ на стороне клиента как “Ошибка тайм-аута при обработке” и в журналах:
AsyncLongRunningServlet Start::Name=http-bio-8080-exec-44::ID=117 AsyncLongRunningServlet End::Name=http-bio-8080-exec-44::ID=117::Time Taken=1 ms. Async Supported? true AppAsyncListener onTimeout AppAsyncListener onError AppAsyncListener onComplete Exception in thread "pool-5-thread-6" java.lang.IllegalStateException: The request associated with the AsyncContext has already completed processing. at org.apache.catalina.core.AsyncContextImpl.check(AsyncContextImpl.java:439) at org.apache.catalina.core.AsyncContextImpl.getResponse(AsyncContextImpl.java:197) at com.journaldev.servlet.async.AsyncRequestProcessor.run(AsyncRequestProcessor.java:27) at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:895) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:918) at java.lang.Thread.run(Thread.java:680)
Обратите внимание, что поток сервлета быстро завершил выполнение, и вся основная работа по обработке выполняется в другом потоке.
Это все для асинхронного сервлета, надеюсь, вам понравилось.