私人笔记:异步-Servlet-组件特性透析及最佳实践攻略
一、异步-Servlet-简介
在Java EE的系列规范中,-Servlet-规范(含-Servlet-、Filter以及Listener等)处在一个核心的地位,包括当下流行的成熟框架(如Strut、Spring MVC、JSF等)中,其都发挥着极其核心的作用,包括基于对Servlet组件的扩展和包装而形成的框架新功能组件等。
当然,此规范也是逐步完善并结合实际应用需求而发展壮大起来。目前Java Web应用中,通行要求支持的规范一般都是3.0版或3.1版(分别对应JavaEE 6和JavaEE 7)。由于JavaEE8的发布一再延期,导致Servlet4.0到目前也没发布。据说下半年一定会随着JavaEE8的发布而发布4.0版,若想先睹为快,可自行到Oracle官网去搜索看看。
简要描述其发展史如下:
SERVLET-版本
关于AsynServlet:我们这里主要讲Servlet-3.x规范里的很少用到的新增特性,即Servlet-异步处理特性(AsynServlet)。所谓异步Servlet-处理,可以这样理解:在服务器的并发请求数量比较大的时候,会产生很多的servlet线程(这些servlet线程由-servlet-容器在线程池中维护),如果每个请求需要耗费的时间比较长(比如,执行了一些IO的处理等),在之前的非异步的servlet中,这些-servlet-线程将会阻塞,严重耗费服务器的资源.而在servlet-3.0中首次出现的异步-servlet-,通过一个单独的新的线程来执行这些比较耗时的任务(也可以把这些任务放到一个自己维护的线程池里),servlet线程立即返回到servlet容器的-servlet-线程池,以便响应其他请求。这样,在降低了系统的资源消耗的同时,也会提升系统的吞吐量。
注意,所谓的异步-Servlet-,简单理解就是把耗时的处理另外安排线程来进行处理,而不至于使当前的Servlet阻塞等待。而实现这种异步处理机制的核心,就是由容器提供的异步上下文来统筹调度实现的。
二、异步-Servlet-关联构成
异步Servlet的所涉及的接口类和API主要包括如下几个标准类:
接口类 | 作用 |
ServletRequest | 在常规的Servlet中,通过此类(或子类)对象获得AsyncContext异步上下文对象 |
ServletResponse | 根据需要实现对异步处理结果的响应输出。 |
AsyncContext | 异步Servlet处理的上下文环境对象。在这里可以把耗时的处理交给其它线程处理,而把Servlet返回给容器线程池来响应其它Servlet请求。 |
AsyncListener | 异步Servlet监听器,监视AsyncContext的状态变化 |
AsyncEvent | 异步事件,作为异步监听器方法接口参数而存在,其实例对象有容器提供。 |
异步-Servlet实现的常规类图关系结构:
异步Servlet结构图
为了总体了解所谓异步-Servlet和传统-Servlet的关系和区别,这里绘制了一幅Servlet组件的总体关系图,以便更好的理解和掌握这个核心组件的应用模式。如下:
Servlet总体关系图
三、异步-Servlet 实战
我们按照上述的关系图以及异步-Servlet-应用场景(比较耗时的业务处理)要求,接下来,就一步步来编程实现异步-Servlet-的应用案例。为了尽可能了解所谓异步处理,本案例把可能相关的类都分开实现了(自诩为最佳实践^_^),也可自行进行简化处理。演示本案例包括这样几个主要的类:
-
²自定义线程池初始化类
-
²接收异步处理的工作线程类;
-
²异步监听器AsyncListener实现类
-
²支持异步处理的自定义Servlet类;
1、创建线程池类:AppContextListener
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;
/**
* Application Lifecycle Listener implementation class AppContextListener
* 启动应用时,创建自己的业务处理线程池,并存放在应用的领域范围内(WebApp级别)
*/
@WebListener
public class AppContextListener implements ServletContextListener {
/**
* Default constructor.
*/
public AppContextListener() {
}
/**
* @see ServletContextListener#contextDestroyed(ServletContextEvent)
*/
public void contextDestroyed(ServletContextEvent sce) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) sce
.getServletContext().getAttribute("executor");
executor.shutdown();
}
/**
* @see ServletContextListener#contextInitialized(ServletContextEvent)
*/
public void contextInitialized(ServletContextEvent sce) {
// 初始化时,创建自己的业务处理线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(100, 200, 50000L,
TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100));
sce.getServletContext().setAttribute("executor", executor);
}
}
2、创建接收异步Servlet业务处理的线程类:AsyncRequestHandler
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.AsyncContext;
public class AsyncRequestHandler implements Runnable {
//接管异步Servlet上下文的对象
private AsyncContext asyncContext;
private int secs; //处理时间
public AsyncRequestHandler() {
}
public AsyncRequestHandler(AsyncContext asyncContext, int secs) {
super();
this.asyncContext = asyncContext;
this.secs = secs;
}
@Override
public void run() {
//注意:在请求和响应时使用AsyncContext对象,
//然后在完成时调用 asyncContext.complete() 方法。
System.out.println("支持异步Servlet? "
+ asyncContext.getRequest().isAsyncSupported());
longTimeWorking(secs); //耗时业务处理
try {
asyncContext.getResponse().setCharacterEncoding("utf-8");
asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
PrintWriter out = asyncContext.getResponse().getWriter();
System.out.println("处理完成,耗时: " + secs + " 毫秒!!");//控制台输出
out.write("处理完成,耗时: " + secs + " 毫秒!!"); //前段输出
} catch (IOException e) {
e.printStackTrace();
}
//完成处理
asyncContext.complete();
System.out.println("异步业务处理完成:asyncContext.complete()");
}
private void longTimeWorking(int secs) {
// 完成前等待的时间
try {
Thread.sleep(secs);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3、创建业务异步上下文监听类:AppAsyncListener
@WebListener
public class AppAsyncListener implements AsyncListener {
/**
* Default constructor.
*/
public AppAsyncListener() {
}
/**
* @see AsyncListener#onComplete(AsyncEvent)
*/
public void onComplete(AsyncEvent ae) throws java.io.IOException {
System.out.println("异步Servlet业务处理完成,触发了此时间:"+System.currentTimeMillis());
System.out.println("AppAsyncListener onComplete");
//可以执行其它资源清理工作
}
/**
* @see AsyncListener#onError(AsyncEvent)
*/
public void onError(AsyncEvent ae) throws java.io.IOException {
System.out.println("AppAsyncListener onError");
//ae.getAsyncContext().getResponse().getWriter().println("业务处理错误");
//像客户端返回错误
}
/**
* @see AsyncListener#onStartAsync(AsyncEvent)
*/
public void onStartAsync(AsyncEvent ae) throws java.io.IOException {
System.out.println("触发异步侦听:AppAsyncListener onStartAsync"+System.currentTimeMillis());
//记录异步处理触发日志信息:根据需要进行
}
/**
* @see AsyncListener#onTimeout(AsyncEvent)
*/
public void onTimeout(AsyncEvent asyncEvent) throws java.io.IOException {
System.out.println("AppAsyncListener onTimeout");
System.out.println("允许的处理时间为:"+asyncEvent.getAsyncContext().getTimeout()+"毫秒。");
//we can send appropriate response to client
ServletResponse response = asyncEvent.getAsyncContext().getResponse();
PrintWriter out = response.getWriter();
out.write("处理超时:==TimeOut Error in Processing=="+System.currentTimeMillis());
}
}
4、创建具有异步处理能力的Servlet类:MyAsyncLongWorkServlet
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;
/**
* Servlet implementation class MyAsyncServlet
*/
@WebServlet(asyncSupported = true, urlPatterns = { "/malServlet" })
public class MyAsyncLongWorkServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public MyAsyncLongWorkServlet() {
super();
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
long startTime = System.currentTimeMillis();
System.out.println("MyAsyncLongWorkServlet 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 AsyncRequestHandler(asyncCtx, secs));
long endTime = System.currentTimeMillis();
System.out.println("MyAsyncLongWorkServlet End::Name="
+ Thread.currentThread().getName() + "::ID="
+ Thread.currentThread().getId() + "::Time Taken="
+ (endTime - startTime) + " ms.");
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
接下来启动服务器(本人用的Tomcat8.0),保证启动正常,在浏览器中输入如下地址回车,观看控制台和前端响应:
MyWebApp/malServlet?time=8999
发表评论 取消回复