詳細解析JSP編程中進度條的設計執行個體

來源:互聯網
上載者:User
js|編程|設計   許多Web應用、公司專屬應用程式涉及到長時間的操作,例如複雜的資料庫查詢或繁重的XML處理等,雖然這些任務主要由資料庫系統或中介軟體完成,但任務執行的結果仍舊要藉助JSP才能發送給使用者。本文介紹了一種通過改進前端表現層來改善使用者感覺、減輕伺服器負載的辦法。

  當JSP調用一個必須長時間啟動並執行操作,且該操作的結果不能(在伺服器端)緩衝,使用者每次請求該頁面時都必須長時間等待。很多時候,使用者會失去耐心,接著嘗試驗擊瀏覽器的重新整理按鈕,最終失望地離開。

  本文介紹的技術是把繁重的計算任務分離開來,由一個獨立的線程運行,從而解決上述問題。當使用者調用JSP頁面時,JSP頁面會立即返回,並提示使用者任務已經啟動且正在執行;JSP頁面自動重新整理自己,報告在獨立線程中啟動並執行繁重計算任務的當前進度,直至任務完成。

   一、類比任務

  首先我們設計一個TaskBean類,它實現java.lang.Runnable介面,其run()方法在一個由JSP頁面(start.jsp)啟動的獨立線程中運行。終止run()方法執行由另一個JSP頁面stop.jsp負責。TaskBean類還實現了java.io.Serializable介面,這樣JSP頁面就可以將它作為JavaBean調用:

  
package test.barBean; import java.io.Serializable; public class TaskBean implements Runnable, Serializable{ private int counter; private int sum; private boolean started; private boolean running; private int sleep; public TaskBean(){ counter = 0; sum = 0; started = false; running = false; sleep = 100; } }


  TaskBean包含的“繁重任務”是計算1+2+3…+100的值,不過它不通過100*(100+1)/2=5050公式計算,而是由run()方法調用work()方法100次完成計算。work()方法的代碼如下所示,其中調用Thread.sleep()是為了確保任務總耗時約10秒。

  
protected void work(){ try{ Thread.sleep(sleep); counter++; sum += counter; } catch (InterruptedException e){ setRunning(false); } }


  status.jsp頁面通過調用下面的getPercent()方法獲得任務的完成狀況:

  
public synchronized int getPercent(){ return counter; }


  如果任務已經啟動,isStarted()方法將返回true:

  
public synchronized boolean isStarted(){ return started; }


  如果任務已經完成,isCompleted()方法將返回true:

  
public synchronized boolean isCompleted(){ return counter == 100; }


  如果任務正在運行,isRunning()方法將返回true:

  
public synchronized boolean isRunning(){ return running; }


  SetRunning()方法由start.jsp或stop.jsp調用,當running參數是true時。SetRunning()方法還要將任務標記為“已經啟動”。調用setRunning(false)表示要求run()方法停止執行。

  
public synchronized void setRunning(boolean running){ this.running = running; if (running) started = true; }


  任務執行完畢後,調用getResult()方法返回計算結果;如果任務尚未執行完畢,它返回null:

  
public synchronized Object getResult(){ if (isCompleted()) return new Integer(sum); else return null; }


  當running標記為true、completed標記為false時,run()方法調用work()。在實際應用中,run()方法也許要執行複雜的SQL查詢、解析大型XML文檔,或者調用消耗大量CPU時間的EJB方法。注意“繁重的任務”可能要在遠程伺服器上執行。報告結果的JSP頁面有兩種選擇:或者等待任務結束,或者使用一個進度條。

  
public void run() { try { setRunning(true); while (isRunning() && !isCompleted()) work(); } finally { setRunning(false); } }


   二、啟動任務

  start.jsp是web.xml部署描述符中聲明的歡迎頁面,web.xml的內容是:

  
<?xml version="1.0" encoding="GB2312"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app> <welcome-file-list> <welcome-file>start.jsp</welcome-file> </welcome-file-list> </web-app>


  start.jsp啟動一個專用的線程來運行“繁重的任務”,然後把HTTP請求傳遞給status.jsp。

  start.jsp頁面利用標記建立一個TaskBean的執行個體,將scope屬性定義為session使得對於來自同一瀏覽器的HTTP請求,其他頁面也能提取到同一個Bean對象。start.jsp通過調用session.removeAttribute("task")確保建立了一個新的Bean對象,而不是提取一箇舊對象(例如,同一個使用者會話中更早的JSP頁面所建立的Bean對象)。

  下面是start.jsp頁面的代碼清單:

  
<% session.removeAttribute("task"); %> <jsp:useBean id="task" scope="session" class="test.barBean.TaskBean"/> <% task.setRunning(true); %> <% new Thread(task).start(); %> <jsp:forward page="status.jsp"/>

  start.jsp建立並設定好TaskBean對象之後,接著建立一個Thread,並將Bean對象作為一個Runnable執行個體傳入。調用start()方法時新建立的線程將執行TaskBean對象的run()方法。

  現在有兩個線程在並發執行:執行JSP頁面的線程(稱之為“JSP線程”),由JSP頁面建立的線程(稱之為“任務線程”)。接下來,start.jsp利用調用status.jsp,status.jsp顯示出進度條以及任務的執行情況。注意status.jsp和start.jsp在同一個JSP線程中運行。

  start.jsp在建立線程之前就把TaskBean的running標記設定成了true,這樣,即使當JSP線程已開始執行status.jsp而任務線程的run()方法尚未啟動,也能夠確保使用者會得到“任務已開始運行”的狀態報表。

  將running標記設定成true、啟動任務線程這兩行代碼可以移入TaskBean構成一個新的方法,然後由JSP頁面調用這個新方法。一般而言,JSP頁面應當盡量少用Java代碼,即我們應當儘可能地把Java代碼放入Java類。不過本例中我們不遵從這一規則,把new Thread(task).start()直接放入start.jsp突出表明JSP線程建立並啟動了任務線程。

  在JSP頁面中操作多線程必須謹慎,注意JSP線程和其它線程實際上是並發執行的,就象在傳統型應用程式中,我們用一個線程來處理GUI事件,另外再用一個或多個線程來處理背景工作。

  不過在JSP環境中,考慮到多個使用者同時請求某一個頁面的情況,同一個JSP頁面可能會在多個線程中同時運行;另外,有時同一個使用者可能會向同一個頁面發出多個請求,雖然這些請求來自同一個使用者,它們也會導致伺服器同時運行一個JSP頁面的多個線程。

  三、任務進度

  status.jsp頁面利用一個HTML進度條向使用者顯示任務的執行情況。首先,status.jsp利用標記獲得start.jsp頁面建立的Bean對象:

<jsp:useBean id="task" scope="session" class="test.barBean.TaskBean"/>

  為了及時反映任務執行進度,status.jsp會自動重新整理。JavaScript代碼setTimeout("location=′status.jsp′", 1000)將每隔1000毫秒重新整理頁面,重新請求status.jsp,不需要使用者幹預。   

<HTML> <HEAD> <TITLE>JSP進度條</TITLE> <% if (task.isRunning()) { %> <SCRIPT LANGUAGE="JavaScript"> setTimeout("location=′status.jsp′", 1000); </SCRIPT> <% } %> </HEAD> <BODY>

  進度條實際上是一個HTML表格,包含10個單元,即每個單元代表任務總體的10%進度。   

<H1 ALIGN="CENTER">JSP進度條</H1> <H2 ALIGN="CENTER">

  結果:   

<%= task.getResult() %><BR> <% int percent = task.getPercent();%> <%= percent %>% </H2> <TABLE WIDTH="60%" ALIGN="CENTER" BORDER=1 CELLPADDING=0 CELLSPACING=2> <TR> <% for (int i = 10; i <= percent; i += 10){ %> <TD WIDTH="10%" BGCOLOR="#000080"> </TD> <% } %> <% for (int i = 100; i > percent; i -= 10){ %> <TD WIDTH="10%"> </TD> <%} %> </TR> </TABLE>

  任務執行情況分下面幾種狀態:正在執行,已完成,尚未開始,已停止:  

<TABLE WIDTH="100%" BORDER=0 CELLPADDING=0 CELLSPACING=0> <TR> <TD ALIGN="CENTER"> <% if (task.isRunning()) { %>

  正在執行   

<% } else { %> <% if (task.isCompleted()) { %>

  完成   

<% } else if (!task.isStarted()){ %>

  尚未開始   

<% } else { %>

  已停止   

<% } %> <% } %> </TD> </TR>

  頁面底部提供了一個按鈕,使用者可以用它來停止或重新啟動任務:  

<TR> <TD ALIGN="CENTER"> <BR> <% if (task.isRunning()) { %> <FORM METHOD="GET" ACTION="stop.jsp"> <INPUT TYPE="SUBMIT" VALUE="停止"> </FORM> <% } else { %> <FORM METHOD="GET" ACTION="start.jsp"> <INPUT TYPE="SUBMIT" VALUE="開始"> </FORM> <% } %> </TD> </TR> </TABLE> </BODY></HTML>

  只要不停止任務,約10秒後瀏覽器將顯示出計算結果5050:

  四、停止任務

  stop.jsp頁面把running標記設定成false,從而停止當前的計算任務:  

<jsp:useBean id="task" scope="session" class="test.barBean.TaskBean"/> <% task.setRunning(false); %> <jsp:forward page="status.jsp"/>

  注意最早的Java版本提供了Thread.stop方法,但JDK從1.2版開始已經不贊成使用Thread.stop方法,所以我們不能直接調用Thread.stop()。

  第一次運行本文程式的時候,你會看到任務的啟動有點延遲;同樣地,第一次點擊“停止”按鈕時也可以看到任務並沒有立即停止運行(特別是如果機器配置較低的話,延遲的感覺更加明顯),這些延遲都是由於編譯JSP頁面導致的。編譯好JSP頁面之後,應答速度就要快多了。

  五、實際應用

  進度條不僅使得使用者介面更加友好,而且對伺服器的效能也有好處,因為進度條會不斷地告訴使用者當前的執行進度,使用者不會再頻繁地停止並重新啟動(重新整理)當前的任務。另一方面,建立單獨的線程來執行背景工作也會消耗不少資源,必要時可考慮通過一個線程池來實現Thread對象的重用。另外,頻繁地重新整理進度頁面也增加了網路通訊開銷,所以務必保持進度頁面簡潔短小。

  在實際應用中,後台執行的繁重任務可能不允許停止,或者它不能提供詳細的執行進度資料。例如,尋找或更新關聯式資料庫時,SQL命令執行期間不允許中途停止??不過如果使用者表示他想要停止或中止任務,程式可以在SQL命令執行完畢後回退事務。

  解析XML文檔的時候,我們沒有辦法獲知已解析內容精確的百分比。如果用DOM解析XML文檔,直到解析完成後才得到整個文檔樹;如果用SAX,雖然可以知道當前解析的內容,但通常不能確定還有多少內容需要解析。在這些場合,任務的執行進度只能靠估計得到。

  估計一個任務需要多少執行時間通常是很困難的,因為它涉及到許多因素,即使用實際測試的辦法也無法得到可靠的結論,因為伺服器的負載隨時都在變化之中。一種簡單的辦法是測量任務每次執行所需時間,然後根據最後幾次執行的平均時間估算。

  如果要提高估計時間的精確度,應當考慮實現一種針對應用特點的演算法,綜合考慮多種因素,例如要執行的SQL語句類型、要解析的XML模式的複雜程度,等等。

  結束語:本文例子顯示出用JSP、Java、HTML和JavaScript構造進度條是相當容易的,真正困難的是如何將它用到實際應用之中,特別是獲得背景工作的進度資訊,但這個問題沒有通用的答案,每一種後台執行的任務都有它自己的特點,必須按照具體情況具體分析。



相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.