Servlet3中异步Servlet特性介绍
在Jave EE 6规范中,关于Servlet 3规范的相关功能增强,一直是让大部分用户忽略的,连直到最新的Spring MVC 3.2才支持Servlet 3的异步调用。这可能跟大部分用户使用的JAVE EE容器依然是旧的有关系(如支持Servlet 3规范的需要Tomcat 7,但目前不少用户还在使用Tomcat 6)。
在本文中,将以实际的例子来讲解下Servlet 3规范中对异步操作的支持。
首先要简单了解,在Servlet 3中,已经支持使用注解的方式去进行Servlet的配置,这样就不需要在web.xml中进行传统的xml的配置了,最常用的注解是使用 @WebServlet、@WebFilter、@WebInitParam,它们分别等价于传统xml配置中 的<Servlet>、<WebFilter>、<InitParam>,其他参数可参考Servlet 3中的规范说明。
下面我们开始了解下,如果不使用异步特性的一个例子,代码如下:
@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); //如果超过10秒,默认用10秒 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) { //故意让线程睡眠 try { Thread.sleep(secs); } catch (InterruptedException e) { e.printStackTrace(); } } }
运行上面的例子,输入
http://localhost:8080/AsyncServletExample/LongRunningServlet?time=8000,则可以看到输出为:
LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103
1. LongRunningServlet Start::Name=http-bio-8080-exec-34::ID=103::Time Taken=8002 ms.
可以观察到,在主线程启动后,servlet线程为了处理longProcessing的请求,足足等待了8秒,最后才输出结果进行响应,这样对于 高并发的应用来说这是很大的瓶颈,因为必须要同步等到待处理的方法完成后,Servlet容器中的线程才能继续接收其他请求,在此之前,Servlet线 程一直处于阻塞状态。
在Servlet 3.0规范前,是有一些相关的解决方案的,比如常见的就是使用一个单独的工作线程(worker thread)去处理这些耗费时间的工作,而Servlet 容器中的线程在把工作交给工作线程处理后则马上回收到Servlet容器中去。比如Tomcat的Comet、WebLogic的的 FutureResponseServlet和WebSphere的Asynchronous Request Dispatcher都是这类型的解决方案。
但只这些方案的弊端是没办法很容易地在不修改代码的情况下迁移到其他Servlet容器中,这就是Servlet 3中要定义异步Servlet的原因所在。
下面我们通过例子来说明异步Servlet的实现方法:
1、 首先设置servlet要支持异步属性,这个只需要设置asyncSupported属性为true就可以了。
2、 因为实际上的工作是委托给另外的线程的,我们应该实现一个线程池,这个可以通过使用Executors框架去实现(具体参考 http://www.journaldev.com/1069/java-thread-pool-example-using-executors- and-threadpoolexecutor一文),并且使用Servlet Context listener去初始化线程池。
3、 我们需要通过ServletRequest.startAsync()方法获得AsyncContext的实例。AsyncContext提供了方法去获 得ServletRequest和ServletResponse的对象引用。它也能使用dispatch()方法去将请求forward到其他资源。
4、 我们将实现Runnable接口,并且在其实现方法中处理各种耗时的任务,然后使用AsyncContext对象去将请求dispatch到其他资源中去 或者使用ServletResponse对象输出。一旦处理完毕,将调用AsyncContext.complete()方法去让容器知道异步处理已经结 束。
5、 我们还可以在AsyncContext对象增加AsyncListener的实现类以实现相关的徽调方法,可以使用这个去提供将错误信息返回给用户(如超时或其他出错信息),也可以做一些资源清理的工作。
我们来看下完成后例子的工程结构图如下:
下面我们看下实现了ServletContextListener类的监听类代码:
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) { // 创建线程池 ThreadPoolExecutor 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(); } }
然后是worker线程的实现代码,如下:
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; } @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(); } //完成异步线程处理 asyncContext.complete(); } private void longProcessing(int secs) { // 休眠指定的时间 try { Thread.sleep(secs); } catch (InterruptedException e) { e.printStackTrace(); } } }
请在这里注意AsyncContext的使用方法,以及当完成异步调用时必须调用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 { @Override public void onComplete(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onComplete"); // 在这里可以做一些资源清理工作 } @Override public void onError(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onError"); //这里可以抛出错误信息 } @Override public void onStartAsync(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onStartAsync"); //可以记录相关日志 } @Override public void onTimeout(AsyncEvent asyncEvent) throws IOException { System.out.println("AppAsyncListener onTimeout"); ServletResponse response = asyncEvent.getAsyncContext().getResponse(); PrintWriter out = response.getWriter(); out.write("TimeOut Error in Processing"); } }
其中请注意可以监听onTimeout事件的使用,可以有效地返回给用户端出错的信息。最后来重新改写下前文提到的测试Servlet的代码如下:
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); // 如果超过10秒则设置为10秒 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."); } }
下面运行这个Servlet程序,输入:
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
但如果我们运行一个time=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主线程很快执行完毕并且所有的处理额外的工作都是在另外一个线程中处理的,不存在阻塞问题。
原文链接:http://www.javacodegeeks.com/2013/08/async-servlet-feature-of-servlet-3.html
相关推荐
NULL 博文链接:https://dacoolbaby.iteye.com/blog/2163367
异步servlet
java servlet 3 异步调用 异步处理 注册监听
NULL 博文链接:https://fanshuyao.iteye.com/blog/1688318
servlet3异步请求,需要安装gradle,执行scripts/gradle-eclipse.bat/sh 然后导入eclipse
Servlet3.0 异步处理 页面推送 Comet 实例
直接进入TestAjax就可以自动找到欢迎页面。
• 异步Servlet • Servlet 3.0 — 易于开发 – 主要关注点 • 增强了API 以便使用SE 5 中新的语言特性 – 例如:批注、泛型(Generics) – 在上次JavaEE 5 中漏掉的Servlet • 部署描述批注 – 现在web.xml 为可选 ...
3-6Tomcat处理源码实现与异步Servlet源码实现(1).mp4
本篇文章主要介绍了servlet3异步原理与实践,详细的介绍了servlet和异步的流程使用,具有一定的参考价值,有兴趣的可以了解一下
在spring 3.2 及以后版本中增加了对请求的异步处理,这篇文章主要介绍了Spring Boot实现异步请求(Servlet 3.0),感兴趣的小伙伴们可以参考一下。
第14章讨论Servlet 3中的一项新特性,用来处理异步的操作;第15章阐述如何通过声明和编程方式来保护Java的Web应用程序;第16章讨论Servlet/JSP应用程序的部署过程,以及部署描述符中的元素;第17章阐述Servlet 3中的...
Spring+ajax+servlet异步完成登录名是否存在,讲解在https://blog.csdn.net/woshilishu/article/details/84619757.
第14章讨论Servlet3中的一项新特性,用来处理异步的操作;第15章阐述如何通过声明和编程方式来保护Java的Web应用程序;第16章讨论Servlet/JSP应用程序的部署过程,以及部署描述符中的元素;第17章阐述Servlet3中的两...
第14章讨论servlet 3中的一项新特性,用来处理异步的操作;第15章阐述如何通过声明和编程方式来保护java的web应用程序;第16章讨论servlet/jsp应用程序的部署过程,以及部署描述符中的元素;第17章阐述servlet 3中的...
ajax异步文件上传,servlet处理
第14章讨论servlet 3中的一项新特性,用来处理异步的操作;第15章阐述如何通过声明和编程方式来保护java的web应用程序;第16章讨论servlet/jsp应用程序的部署过程,以及部署描述符中的元素;第17章阐述servlet 3中的...
Servlet3.1规范中文最终版,可以参考实现Servlet异步请求与回调机制
该demo实现前端无刷新请求,实现图片批量上传,后台存储数据,并将数据通过JSON形式反馈给android端显示网络图片
servlet 3.0 聊天室 推送 异步