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");
            }
        });
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM