在深入了解什么是异步Servlet之前,让我们尝试了解为什么需要它。 假设我们有一个Servlet,处理时间很长,如下所示。
LongRunningServlet.java
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 secondsif (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 finishingtry {Thread.sleep(secs);} catch (InterruptedException e) {e.printStackTrace();}}}
如果我们通过浏览器在URL上方为http://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000
servlet上方收到响应,则响应为“处理完成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.
因此,尽管大多数处理与Servlet请求或响应无关,但我们的Servlet线程运行了大约8+秒。
这可能导致线程匮乏 –由于在所有处理完成之前我们的Servlet线程被阻塞,因此如果服务器收到大量要处理的请求,它将达到servlet线程的最大限制,并且进一步的请求将出现Connection Refused错误。
在Servlet 3.0之前,有针对这些长时间运行的线程的特定于容器的解决方案,我们可以生成单独的工作线程来执行繁重的任务,然后将响应返回给客户端。 启动工作线程后,Servlet线程将返回到Servlet池。 Tomcat的Comet,WebLogic的FutureResponseServlet和WebSphere的异步请求分派器是异步处理实现的一些示例。
特定于容器的解决方案的问题在于,在不更改应用程序代码的情况下我们无法移至其他servlet容器,这就是为什么在Servlet 3.0中添加了Async Servlet支持以为Servlet中的异步处理提供标准方式的原因。
异步Servlet实现
让我们看一下实现异步servlet的步骤,然后为上述示例提供异步支持的servlet。
- 首先,我们要提供异步支持的servlet应该具有@WebServlet 批注 ,其asyncSupported值为true 。
- 由于实际工作将委托给另一个线程,因此我们应该有一个线程池实现。 我们可以使用Executors框架创建线程池,并使用servlet上下文侦听器启动线程池。
- 我们需要通过
ServletRequest.startAsync()
方法获取AsyncContext的实例。 AsyncContext提供了获取ServletRequest和ServletResponse对象引用的方法。 它还提供了使用dispatch()方法将请求转发到另一个资源的方法。 - 我们应该有一个Runnable实现 ,在这里我们将进行繁重的处理,然后使用AsyncContext对象将请求分派到另一个资源,或者使用ServletResponse对象写入响应。 处理完成后,我们应调用AsyncContext.complete()方法以使容器知道异步处理已完成。
- 我们可以将AsyncListener实现添加到AsyncContext对象中以实现回调方法–我们可以使用它为异步线程处理过程中出现错误或超时的情况提供对客户端的错误响应。 我们还可以在此处进行一些清理活动。
一旦我们完成了异步Servlet示例示例的项目,它将如下图所示。
在Servlet上下文侦听器中初始化辅助线程池
AppContextListener.java
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 poolThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));servletContextEvent.getServletContext().setAttribute("executor",executor);}public void contextDestroyed(ServletContextEvent servletContextEvent) {ThreadPoolExecutor executor = (ThreadPoolExecutor) servletContextEvent.getServletContext().getAttribute("executor");executor.shutdown();}}
该实现非常简单,如果您不熟悉Executors框架,请阅读Thread Pool Executor 。
有关侦听器的更多详细信息,请阅读Servlet Listener Tutorial 。
工作线程实现
AsyncRequestProcessor.java
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;}@Overridepublic 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 processingasyncContext.complete();}private void longProcessing(int secs) {// wait for given time before finishingtry {Thread.sleep(secs);} catch (InterruptedException e) {e.printStackTrace();}}
}
请注意,AsyncContext的用法及其在获取请求和响应对象,然后通过complete()方法调用完成异步处理的用法。
AsyncListener实现
AppAsyncListener.java
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 {@Overridepublic void onComplete(AsyncEvent asyncEvent) throws IOException {System.out.println("AppAsyncListener onComplete");// we can do resource cleanup activity here}@Overridepublic void onError(AsyncEvent asyncEvent) throws IOException {System.out.println("AppAsyncListener onError");//we can return error response to client}@Overridepublic void onStartAsync(AsyncEvent asyncEvent) throws IOException {System.out.println("AppAsyncListener onStartAsync");//we can log the event here}@Overridepublic void onTimeout(AsyncEvent asyncEvent) throws IOException {System.out.println("AppAsyncListener onTimeout");//we can send appropriate response to clientServletResponse response = asyncEvent.getAsyncContext().getResponse();PrintWriter out = response.getWriter();out.write("TimeOut Error in Processing");}}
注意onTimeout()方法的实现,在该方法中,我们向客户端发送超时响应。
这是我们异步servlet的实现,请注意使用AsyncContext和ThreadPoolExecutor进行处理。
AsyncLongRunningServlet.java
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 secondsif (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.");}}
运行异步Servlet
现在,当我们在servlet上运行,URL为http://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)
注意,servlet线程快速完成了执行,所有主要的处理工作都在其他线程中进行。
这就是异步Servlet的全部,希望您喜欢它。
- 下载AsyncServletExample项目
翻译自: https://www.javacodegeeks.com/2013/08/async-servlet-feature-of-servlet-3.html