jsp 异步处理

 

一.  概述

  异步处理功能可以节约容器线程。你应该将此功能 使用在长时间运行的操作上。此功能的作用是释放正在 等待完成的线程,使该线程能够被另一请求所使用。

二. 编写异步Servlet和过滤器

  WebServlet和WebFilter注解类型可能包含新的 asyncSupport属性。要编写支持异步处理的Servlet或过 滤器,需设置asyncSupported属性为true:

@WebServlet(asyncSupported=true ...)
@WebFilter(asyncSupported=true ...)

  此外,也可以在部署文件里面指定这个描述符。例 如,下面的 Servlet 配置为支持异步处理:

<servlet>
<servlet-name>AsyncServlet</servlet-name>
<servlet-class>servlet.MyAsyncServlet</servlet-class>
<async-supported>true</async-supported>
</servlet>

  Servlet或过滤器要支持异步处理,可以通过调用 ServletRequest的startAsync方法来启动一个新线程。这 里有两个startAsync的重载方法:

AsyncContext startAsync() throws java.lang.IllegalStateExceptio
n
AsyncContext startAsync(ServletRequest servletRequest,
ServletResponse servletResponse) throws
java.lang.IllegalStateException

  这两个重载方法都返回一个AsyncContext的实例, 这个实例提供各种方法并且包含ServletRequest和 ServletResponse。第一个重载实例比较简单并且使用方 便。由此生成的asynccontext实例将包含原生的 ServletRequest和ServletResponse。第二个允许您将原来 的ServletRequest和ServletResponse进行重写封装后传给 asynccontext。需要注意的是,你只能传递原生的 ServletRequest和ServletResponse或它们的封装到 startAsync第二种重载实例。

  注意,startAsync重复调用将返回相同的 asynccontext。若一个Servlet或过滤器调用startAsync时 不支持异步处理,将抛出java.lang.illegalstateexception 异常。还请注意,asynccontext的start方法是非阻塞的, 所以下一行代码仍将执行,即使还未调度线程启动。

三. 编写异步Servlets

  写一个异步或异步Servlet或过滤器比较简单。当有 一个需要相当长的时间完成的任务时,需要创建一个异 步的Servlet或过滤器。在异步Servlet或过滤器类中需要 做如下操作:

  (1)调用ServletRequest中的startAsync方法。该 startAsync返一个AsyncContext 。

  (2)调用AsyncContext的setTimeout(),传递容器 等待任务完成的超时时间的毫秒数。此步骤是可选的, 但如果你不设置超时,容器的将使用默认的超时时间。 如果任务未能在指定的超时时间内完成,将会抛出一个 超时异常。

  (3)调用asyncContext.start,传递一个Runnable来 执行一个长时间运行的任务。

  (4)调用Runnable的asynccontext.complete或 asynccontext.dispatch方法来完成任务。

这里是一个异步Servlet的doGet或doPost方法的框 架:

final AsyncContext asyncContext = servletRequest.startAsync();
asyncContext.setTimeout( ... );
asyncContext.start(new Runnable() {
@Override
public void run() {
// long running task
asyncContext.complete() or asyncContext.dispatch()
}
})

  下面例子显示了支持异步处理的 Servlet。

AsyncDispatchServlet 类

package servlet;

import java.io.IOException;
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(name = "AsyncDispatchServlet", urlPatterns = { "/asyncDispatch" }, asyncSupported = true)
public class AsyncDispatchServlet extends HttpServlet {
    private static final long serialVersionUID = 222L;

    @Override
    public void doGet(final HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        final AsyncContext asyncContext = request.startAsync();
        request.setAttribute("mainThread", Thread.currentThread().getName());
        asyncContext.setTimeout(5000);
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
// long-running task
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                }
                request.setAttribute("workerThread", Thread.currentThread().getName());
                // 调度到其它资源取完成任务 例如threadNames.jsp页面
                asyncContext.dispatch("/threadNames.jsp");
            }
        });
    }
}

这个Servlet支持异步处理且其长期运行的 任务就是简单地休眠三秒钟。为了证明这个长时间运行 的任务是在不同的线程中执行的,而不是在主线程中执 行的(即执行 Servlet的doGet方法),它将主线程的名 字和工作线程的ServletRequest分派到一个 threadNames.jsp页面。该threadNames.jsp页面显示mainThread和WorkerThread变量。它 们应打印不同的线程名字。

threadNames.jsp页面

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
mainThread: ${mainThread} <br />
workThread: ${workerThread}

</body>
</html>

注意,你需要在任务结束后调用asynccontext的 dispatch或complete,所以它不会等待,直到它超时。

你可以把你的这个URL输入到浏览器来测试 servlet:

  上图显示了主线程的名称和工作线程的名称。你 在你的浏览器中看到的可能是不同的,但打印出的线程 名字会有所不同,证明了工作线程与主线程不同.

  除了调度到其他资源去完成任务,你也可以调用 AsyncContext的complete方法。此方法通知servlet容器 该任务已完成。

  作为第二个例子,思考一下清单  AsyncCompleteServlet Servlet。 该Servlet每秒发送一次进度更新,使用户能够监测进展 情况。它发送HTML响应和一个简单的JavaScript代码来 更新HTML div元素。

package servlet;

import java.io.IOException;
import java.io.PrintWriter;
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;

public class AsyncCompleteServlet extends HttpServlet {
    private static final long serialVersionUID = 78234L;

    @Override
    public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        final PrintWriter writer = response.getWriter();
        writer.println("<html><head><title>" + "Async Servlet</title></head>");
        writer.println("<body><div id='progress'></div>");
        final AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(60000);
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                System.out.println("new thread:" + Thread.currentThread());
                for (int i = 0; i < 10; i++) {
                    writer.println("<script>");
                    writer.println("document.getElementById(" + "'progress').innerHTML = '" + (i * 10) + "% complete'");
                    writer.println("</script>");
                    writer.flush();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                    }
                }
                writer.println("<script>");
                writer.println("document.getElementById(" + "'progress').innerHTML = 'DONE'");
                writer.println("</script>");
                writer.println("</body></html>");
                asyncContext.complete();
            }
        });
    }
}

部署描述符(web.xml文 件)

<servlet>
      <servlet-name>AsyncComplete</servlet-name>
      <servlet-class>servlet.AsyncCompleteServlet</servlet-class>
      <async-supported>true</async-supported>
  </servlet>
  <servlet-mapping>
      <servlet-name>AsyncComplete</servlet-name>
      <url-pattern>/async-supported</url-pattern>
  </servlet-mapping>
  

四. 异步监听器

  为支持Servlet和过滤器配合执行异步操作,Servlet 3.0还增加了asynclistener接口用于接收异步处理过程中 发生事件的通知。AsyncListener接口定义了如下方法, 当某些事件发生时调用:

  

void onStartAsync(AsyncEvent event)

在异步操作启动完毕后调用该方法

void onComplete(AsyncEvent event)

在异步操作完成后调用该方法。

void onError(AsyncEvent event)

在异步操作失败后调用该方法.

void onTimeout(AsyncEvent event)

在异步操作超时后调用该方法,即当它未能在指定 的超时时间内完成时。

  所有四种方法可以分别通过它们的 getAsyncContext、getSuppliedRequest和 getSuppliedResponse方法,从AsyncContext、 ServletRequest、ServletResponse中获取相关的 AsyncEvent。

  这里有一个例子,MyAsyncListener类 实现AsyncListener接口,以便在异步操作事件发生时, 它能够得到通知。请注意,和其他网络监听器不同,你 不需要通过@WebListener注解来实现。

  

package listener;

import java.io.IOException;
import javax.servlet.AsyncEvent;
import javax.servlet.AsyncListener;

// 不需要标注@WebListener
public class MyAsyncListener implements AsyncListener {
    @Override
    public void onComplete(AsyncEvent asyncEvent) throws IOException {
        System.out.println("onComplete");
    }

    @Override
    public void onError(AsyncEvent asyncEvent) throws IOException {
        System.out.println("onError");
    }

    @Override
    public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
        System.out.println("onStartAsync");
    }

    @Override
    public void onTimeout(AsyncEvent asyncEvent) throws IOException {
        System.out.println("onTimeout");
    }
}

  由于AsyncListener类不是用@WebListener注解的, 因此必须为AsyncContext手动注册一个AsyncListener监 听器,用于接收所需要的事件。通过调用addListener方 法为AsyncContext注册一个AsyncListener监听器:

 Class lClass;
        try {
            lClass = Class.forName("listener.MyAsyncListener");
               AsyncListener al = asyncContext.createListener(lClass);
            asyncContext.addListener(al);
        } catch (ClassNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

使用asynclistener

package servlet;

import java.io.IOException;
import javax.servlet.AsyncContext;
import javax.servlet.AsyncListener;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import listener.MyAsyncListener;

@WebServlet(name = "AsyncListenerServlet", urlPatterns = { "/asyncListener" }, asyncSupported = true)
public class AsyncListenerServlet extends HttpServlet {
    private static final long serialVersionUID = 62738L;

    @Override
    public void doGet(final HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        final AsyncContext asyncContext = request.startAsync();
        asyncContext.setTimeout(5000);
        
         Class lClass;
        try {
            lClass = Class.forName("listener.MyAsyncListener");
               AsyncListener al = asyncContext.createListener(lClass);
            asyncContext.addListener(al);
        } catch (ClassNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
      
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                }
                String greeting = "hi from listener";
                System.out.println("wait....");
                request.setAttribute("greeting", greeting);
                asyncContext.dispatch("/test.jsp");
            }
        });
    }
}

 

posted @ 2019-04-17 11:14  江期玉  阅读(1817)  评论(0编辑  收藏  举报