Рубрики
Без рубрики

Пример Асинхронного Сервлета

Пример асинхронного сервлета, пример асинхронного сервлета, asynccontext сервлета, учебник по асинхронному сервлету сервлета 3, пример кода асинхронного сервлета tomcat

Автор оригинала: 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 была добавлена поддержка асинхронных сервлетов, чтобы обеспечить стандартный способ асинхронной обработки в сервлетах.

Асинхронная Реализация Сервлета

Давайте рассмотрим шаги по реализации асинхронного сервлета, а затем мы предоставим асинхронный поддерживаемый сервлет для приведенного выше примера.

  1. Прежде всего, сервлет, в котором мы хотим обеспечить поддержку асинхронности, должен иметь @WebServlet | аннотацию с поддержкой ASYNC значением true . Поскольку фактическая работа должна быть делегирована другому потоку, у нас должна быть реализация пула потоков. Мы можем создать
  2. пул потоков с помощью платформы исполнителей и использовать servletcontextlistener для инициализации пула потоков. Нам нужно получить экземпляр
  3. AsyncContext через ServletRequest.startAsync() метод. AsyncContext предоставляет методы для получения ссылок на объекты ServletRequest и ServletResponse. Он также предоставляет метод для пересылки запроса на другой ресурс с использованием метода dispatch (). У нас должна быть
  4. Запускаемая реализация , в которой мы будем выполнять тяжелую обработку, а затем использовать объект 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)

Обратите внимание, что поток сервлета быстро завершил выполнение, и вся основная работа по обработке выполняется в другом потоке.

Это все для асинхронного сервлета, надеюсь, вам понравилось.