`

JSP编程进度条设计

阅读更多

许多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页面利用<jsp:useBean>标记创建一个TaskBean的实例,将scope属性定义为session使得对于来自同一浏览器的HTTP请求,其他页面也能提取到同一个Bean对象。start.jsp通过调用session.removeAttribute("task")确保<jsp:useBean>创建了一个新的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利用<jsp:useBean>标记获得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>



<ODY>



  进度条实际上是一个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构造进度条是相当容易的,真正困难的是如何将它用到实际应用之中,特别是获得后台任务的进度信息,但这个问题没有通用的答案,每一种后台执行的任务都有它自己的特点,必须按照具体情况具体分析。
分享到:
评论

相关推荐

    JSP编程进度条设计实例

    JSP编程进度条设计实例,相当经典。

    shell脚本编程100例JSP编程进度条设计实例.docx

    。。。

    shell脚本编程100例JSP编程进度条设计实例.pdf

    。。。

    javaweb开发JSP资料大全

    JSP安全编程实例浅析、JSP编程进度条设计实例、JSP的运行内幕、JSP和IIS的最佳解决方案实例分析、jsp内置对象--session对象和out对象、JSP中request属性的用法、用WebWork、JSP、Velocity建立注册页面、在JSP中使用...

    JSP帮助手册_CN

    本手册包括 JSP编程进度条设计实例 JSP连接各类数据库大全 及JSP和servlet概述 struts傻瓜式入门等多种实用性开发参考资料

    jsp编程技巧集锦

    JSP编程中常用的js技术 2. 在下拉列表框里选择一个值后跳出新窗口? 3. 在JSP中启动execl? 4. 两级下拉列表框联动菜单? 5. java中如何把一个目录下的文件移到另一个指定的目录? 6. 制作表格线? 7...

    JSP实用技巧集合,jsp编程的一些小技巧总结

    jsp编程的一些小技巧总结,绝对实用。包括JSP编程中常用的js技术。 1.JSP编程中常用的js技术 2. 在下拉列表框里选择一个值后跳出新窗口? 3. 在JSP中启动execl? 4. 两级下拉列表框联动菜单? 5. java中如何把一个目录...

    h_JAVA 2应用编程150例.rar

    实例2 设计软件启动界面 9 实例3 实现多色窗口 11 实例4 切分窗口 13 实例5 丰富多彩的按钮 15 实例6 在窗口中显示背景图 16 实例7 在窗体中绘制图形 18 实例8 利用JNI实现窗口特效 20 实例9 文件选择对话框 24 实例...

    编程技巧(全部工程源代码,物超所值)

    软件在线升级程序的设计 用C++Builder 6.0实现远程控制 利用C#调用Microsoft.Win32命名空间中的类实现对注册表的读取管理 用C#开发手机短信收发程序 远程计算机重启原理及实现 VC++6.0下利用互斥量同步线程来...

    java应用软件程序设计

    这里边包括:第1章 Java图形用户界面编程 1 实例1 布局管理 2 实例2 设计软件启动界面 9 实例3 实现多色窗口 11 实例4 切分窗口 13 实例5 丰富多彩的按钮 15 实例6 在窗口中显示背景图 16 实例...

    AJAX基础概念、核心技术与典型案例(内涵动态实例)

    程序描述:本章使用Prototype封装的Ajax对象实现上传文件时显示进度条的功能。当上传文件时,客户端同时显示文件上传的进度,从而及时了解文件传送情况。 /test.html 上传文件 /periodcal.jsp 上传文件...

    深入浅出Struts 2 .pdf(原书扫描版) part 1

    第17章 进度条 263 17.1 Execute and Wait拦截器 263 17.2 使用Execute and Wait拦截器 264 17.3 使用一个自定义的“等待”页面 265 17.4 小结 266 第18章 定制拦截器 267 18.1 Interceptor接口 267 18.2 编写一个...

    JAVA上百实例源码以及开源项目

    例如,容易实现协议的设计。 Java EJB中有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;...

    JAVA上百实例源码以及开源项目源代码

    例如,容易实现协议的设计。 Java EJB中有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;...

    深入浅出Struts2(附源码)

    第17章进度条 263 17.1 Execute and Wait拦截器 263 17.2 使用Execute and Wait拦截器 264 17.3 使用一个自定义的“等待”页面 265 17.4 小结 266 第18章定制拦截器 267 18.1 Interceptor接口 267 18.2 编写...

Global site tag (gtag.js) - Google Analytics