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
相关推荐
在这篇文章中,我们将深入探讨Servlet3的一个关键特性——异步Servlet处理,以及它如何改变了传统的请求-响应模型。 首先,让我们了解传统Servlet的工作方式。在Servlet2.x时代,每个HTTP请求都会创建一个新的线程...
在本篇文章中,我们将深入探讨Servlet3.0的主要新特性,包括注解、异步处理、容器初始化参数以及模块化部署等。 首先,Servlet3.0最显著的特征之一是广泛使用注解(Annotation)。传统的Servlet配置通常需要在web....
异步Servlet是Java Servlet API的一个重要特性,它允许在Servlet容器中执行异步处理,显著提高了Web应用程序的性能和响应性。在传统的Servlet中,请求处理是同步的,即Servlet容器接收请求,调用Servlet的service...
在提供的`Servlet3_0_asyncDemo`压缩包文件中,可能包含了一个简单的异步处理示例。这个示例可能演示了如何创建一个Servlet,启用异步处理,启动异步上下文,并注册监听器来监控异步操作的生命周期。通过对这个示例...
Servlet3异步请求是Java Web开发中的一个重要特性,它允许开发者在处理HTTP请求时启用非阻塞模式,显著提高了Web应用程序的性能和响应能力。在Servlet 3.0规范中,这一特性被引入,使得服务器可以更有效地管理资源,...
【基于jsp+servlet+Ajax异步登陆模拟web项目】是一个典型的Web开发实例,它整合了三种核心技术:JavaServer Pages(JSP)、Servlet以及Asynchronous JavaScript and XML(Ajax)。这个项目的核心目的是实现用户登录...
本篇文章主要介绍了servlet3异步原理与实践,详细的介绍了servlet和异步的流程使用,具有一定的参考价值。 一、什么是Servlet Servlet 是基于 Java 的 Web 组件,由容器进行管理,来生成动态内容。像其他基于 Java...
首先,我们需要创建一个异步Servlet。在Servlet的`doGet`或`doPost`方法中,我们可以调用`AsyncContext.start()`来启动一个新的线程,这个线程将在后台执行耗时的操作。例如: ```java protected void doGet...
Servlet 3.0引入了一种全新的特性,即异步处理能力,这极大地提高了Web应用程序的性能和响应性,尤其是在处理长时间运行的任务时。在传统的Servlet中,请求处理线程会一直占用,直到整个请求生命周期结束,这可能...
### ORACLE官方培训servlet-3新特性中文版 #### 一、概述 在Web开发领域,...ORACLE官方培训提供的servlet-3新特性中文版资料不仅介绍了这些新特性,还提供了实用的示例代码,帮助开发者快速掌握这些关键技术。
在IT行业中,Web开发是不可或缺的一部分,而Servlet、图片批量上传和Ajax异步技术则是构建高效、用户友好的Web应用的关键技术。以下是对这些知识点的详细说明: **Servlet上传** Servlet是Java EE平台中用于扩展...
在Servlet3中,不再需要XML配置文件来定义Servlet、Filter和Listener,可以直接使用注解进行声明式配置。这简化了应用部署描述符(web.xml)的编写,提高了开发效率。 3. **动态注册**: 开发者可以在代码中动态...
在Servlet3中,开发者可以通过在类上使用`@PostConstruct`和`@PreDestroy`注解来定义初始化和销毁方法,从而更好地控制Servlet的生命周期。 4. **WebSocket支持** Servlet3.0规范加入了对WebSocket协议的支持,...
在使用 Spring Boot 实现异步请求时,需要在 Servlet 中添加 asyncSupported = true 属性,以启用异步处理。在 Filter 中也需要添加 asyncSupported = true 属性,以便在 Filter 中也能启用异步处理。 在 Spring ...
一个显著的新特性是支持异步Servlet。通过实现`AsyncContext`接口,Servlet可以将控制权交还给容器,然后在后台执行长时间运行的任务,而不会阻塞HTTP线程。这提高了系统的并发性能,特别是在处理I/O密集型任务时。...
Servlet3 API文档是Java Web开发领域中的重要参考资料,它详细阐述了Servlet的相关接口、类以及方法,为开发者提供了全面的指南。Servlet技术是用于构建动态Web应用程序的核心组件,它允许服务器端处理HTTP请求并...
3-6Tomcat处理源码实现与异步Servlet源码实现(1).mp4
**Spring框架**是Java企业级应用开发中的核心框架,提供了依赖注入(DI)、面向切面编程(AOP)等特性,使得应用程序更加模块化、易于维护。在本示例中,Spring将作为后台服务层,负责处理业务逻辑。 **Ajax...
Servlet3.0是Java EE平台中的一个重要组成部分,它在Servlet2.5的基础上引入了许多新特性,极大地提高了开发效率和灵活性。以下是对Servlet3.0主要特性的详细解释: 1. **注解配置**: 在Servlet3.0中,我们可以...