构建响应

一个响应包含服务器和客户端之间传递的数据。所有的响应都实现了ServletResponse接口。这些接口定义了允许用户做以下事情的方法:

·  获取用来发送数据到客户端的输出流。要发送字符数据,可以使用响应的getWriter方法返回的PrintWriter。要发送MIME主体响应中的二进制数据,则可以使用getOutputStream返回的ServletOutputStream。而对于二进制和正文的复合数据(例如创建一个多部分响应),使用ServletOutputStream手工管理字符部分。

·  指出响应返回的内容类型(content type)(例如,text/html)。一个内容类型名的记录由Internet 网络号分配机构(Internet Assigned Numbers Authority,IANA)保存在:

ftp://ftp.isi.edu/in-notes/iana/assignments/media-types

·  指定是否缓冲输出。缺省情况下,任何写入到输出流的内容都是立即发送给客户端的。缓冲区允许在真正向客户端返回内容之前先填写内容,这样servlet就有更多的时间来设置适当的状态代码、头部,或转到其他Web资源。

·  设置地区化信息。

HTTP响应对象HttpServletResponse含有表示HTTP头的字段,如:

·  状态代码,用来指示请求不能得到满足的原因。

·  Cookies,用来在客户端中存储应用程序专属(application-specific)的信息。有时候cookies用于维护跟踪用户会话的标识符(见会话跟踪)。

在Duke书店中,BookDetailsServlet产生一个HTML页面,用于显示由servlet从数据库中获得的有关书的信息。servlet首先设置响应的头部:响应的内容类型和缓冲区大小。servlet缓冲区页面的内容,以便数据库访问能产生一个异常,这个异常将导致转到一个错误页。通过缓冲响应,客户端就不会看到一个Duke’s Bookstore页面的部分与错误页面的拼接。doGet方法然后从响应中获取PrintWriter。

为了填充响应,servlet首先将请求分派到BannerServlet,BannerServlet为应用程序中所有的servlet产生一个公共的标志(banner)。这一过程在在响应中包括其他资源中讨论。然后,servlet从请求参数中获取书标识符,并使用标识符从书店数据库中获取与书有关的信息。最后,servlet产生HTML标记,用来描述书信息并调用PrintWriter上的close方法向客户端提交响应。

public class BookDetailsServlet extends HttpServlet {
   public void doGet (HttpServletRequest request,
      HttpServletResponse response)
      throws ServletException, IOException {
    // set headers before accessing the Writer
    response.setContentType("text/html");
    response.setBufferSize(8192);
    PrintWriter out = response.getWriter();

      // then write the response
    out.println("<html>" +
      "<head><title>+
      messages.getString("TitleBookDescription")
      +</title></head>");

      // Get the dispatcher; it gets the banner to the user
    RequestDispatcher dispatcher =
      getServletContext().
      getRequestDispatcher("/banner");
    if (dispatcher != null)
      dispatcher.include(request, response);

      //Get the identifier of the book to display
    String bookId = request.getParameter("bookId");
    if (bookId != null) {
      // and the information about the book
      try {         BookDetails bd =  
         bookDB.getBookDetails(bookId);
        ...
        //Print out the information obtained
        out.println("<h2>" + bd.getTitle() + "</h2>" +
        ...
      } catch (BookNotFoundException ex) {
        response.resetBuffer();  
       throw new ServletException(ex);
      }
    }
    out.println("</body></html>");
    out.close();
  }
}

BookDetailsServlet产生一个如图14-2所示的页面:

Book Details

图14-2 书的详细资料

 

过滤请求和响应

一个过滤器就是一个对象,它可以转换请求或响应的头部和内容(或同时转换头部和内容)。与Web组件不同,过滤器通常本身并不创建响应。相反,过滤器提供可以附着到任何Web资源上的功能性。因此,过滤器不应该对他们所过滤的Web资源有任何的依赖,以便可以组合不止一种类型的Web资源。过滤器可以执行的主要任务有:

·  查询请求并作出相应的行动。

·  阻塞请求-响应对,使其不能进一步传递。

·  修改请求的头部和数据。用户可以提供自定义的请求。

·  修改响应的头部和数据。用户可以通过提供定制的响应版本实现。

·  与外部资源进行交互。

过滤器的应用程序包括验证、日志、图片转换、数据压缩、加密、令牌化流和XML转换等等。

用户可以配置一个一个Web资源,使其通过由零个、一个或更多过滤器按照一定顺序组成的过滤器链的过滤。这个过滤器链是在包含该组件的Web应用被部署并且在Web容器转载该组件时实例化了该Web应用时创建的。

概括起来,涉及到过滤器的使用的任务包括:

·  编写过滤器。

·  编写自定义的请求和响应。

·  为每个Web资源指定过滤器链。

编写过滤器

过滤器API由javax.servlet包里的Filter、 FilterChain和FilterConfig接口定义。用户可以通过执行Filter接口定义一个过滤器。在这个接口中最重要的方法是doFilter,这个方法以请求、响应和过滤器链对象作为传递的参数。该方法执行以下动作:

·  检查请求的头部。

·  如果希望修改请求的头部或数据,则定制请求对象。

·  如果希望修改响应的头部或数据,则定制响应对象。

·  调用过滤器链中的下一个实体。如果当前的过滤器是链中最后一个过滤器,并且该链以目标Web组件或静态资源结束,则下一个实体就是链尾的资源;否则,下一个实体就是在WAR中配置的下一个过滤器。可通过调用链对象上的doFilter 方法来调用下一个实体(在它被调用时所使用的请求和响应中或者在它可能已经创建的包装好的版本中传递)。换句话说,也可以通过不调用下一个实体来阻塞请求。在后面这种情况,使用过滤器来填充响应。

·  在调用链中的下一个过滤器之后,检查响应的头部。

·  在处理过程中抛出一个异常来指示错误。

除了doFilter之外,用户必须执行init和destroy方法。当过滤器已经实例化时,由容器调用init方法。如果用户希望将参数初始化传递给过滤器,这需要从已经传递给init的FilterConfig对象中获取。

当进帐和收据servlet被访问时,Duke书店应用程序使用过滤器HitCounterFilterOrderFilter来增加和记录计数器的值。

在doFilter方法中,两个过滤器都从过滤器配置对象中获取servlet上下文,以便它们可以访问作为上下文属性存储的计数器。在过滤器完成应用程序专属的过程后,过滤器调用过滤器链对象上的doFilter方法,这个过滤器链对象是作为参数传递给doFilter方法的。这些被省略的代码将在下一节讨论。

public final class HitCounterFilter implements Filter {
  private FilterConfig filterConfig = null;

    public void init(FilterConfig filterConfig)
    throws ServletException {
    this.filterConfig = filterConfig;
  }
  public void destroy() {
    this.filterConfig = null;   }
  public void doFilter(ServletRequest request,
    ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
    if (filterConfig == null)
      return;
    StringWriter sw = new StringWriter();
    PrintWriter writer = new PrintWriter(sw);
    Counter counter = (Counter)filterConfig.
      getServletContext().
      getAttribute("hitCounter");
    writer.println();
    writer.println("===============");
    writer.println("The number of hits is: " +
      counter.incCounter());
    writer.println("===============");
    // Log the resulting string
    writer.flush();
    filterConfig.getServletContext().
      log(sw.getBuffer().toString());
    ...
    chain.doFilter(request, wrapper);
    ...
  }
}

编写定制的请求和响应

过滤器有很多方法修改请求和响应。例如,过滤器可以为请求添加一个属性中或者在响应中插入一个数据。在Duke书店的例子中,HitCounterFilter在响应中插入一个计数器的值。

过滤器要修改响应,通常必须在响应返回到客户端之前捕捉到这个响应。其方法是将一个替代流(stand-in stream)传递给产生响应的servlet。这个替代流防止在servlet完成时关闭原先的响应流,并且允许过滤器修改servlet的响应。

为了传递这个替代流给servlet,过滤器创建了一个响应包装器以返回替代流,该包装器重载了getWriter或getOutputStream方法。包装器被传递到过滤器链的doFilter方法。包装器方法缺省地接通已包装的请求或响应对象。这个方法与有名的包装器或Decorator模式相似 (这个模式在Design Patterns, Elements of Reusable Object-Oriented Software (Addison-Wesley, 1995年)中有描述)。接下来的几节将描述如何符合先前描述的计数器过滤器和使用包装器的其他过滤器类型。

为了重载请求方法,用户可以将请求包装在一个对象中,该对象扩展了ServletRequestWrapperHttpServletRequestWrapper。而为了重载响应方法,用户可以将响应包装在扩展了ServletResponseWrapperHttpServletResponseWrapper的对象中。

HitCounterFilter在CharResponseWrapper中包装了响应。已包装的响应被传递到过滤器链的下一个对象中,即BookStoreServlet。BookStoreServlet将响应写入CharResponseWrapper创建的流中。当chain.doFilter返回,HitCounterFilter从PrintWriter中获取servlet的响应,并且将其写入缓冲区。过滤器在缓冲区中插入计数器的值,重新设置响应头部的内容长度字段,最后将缓冲区的内容写入响应流。

PrintWriter out = response.getWriter();
CharResponseWrapper wrapper = new CharResponseWrapper(
  (HttpServletResponse)response); chain.doFilter(request, wrapper);
CharArrayWriter caw = new CharArrayWriter();
caw.write(wrapper.toString().substring(0,
  wrapper.toString().indexOf("</body>")-1));
caw.write("<p>\n<center>" +
  messages.getString("Visitor") + "<font color='red'>" +
  counter.getCounter() + "</font></center>");
caw.write("\n</body></html>");
response.setContentLength(caw.toString().length());
out.write(caw.toString());
out.close();
  public class CharResponseWrapper extends
  HttpServletResponseWra pper {   private CharArrayWriter output;

  public String toString() {
    return output.toString();
  }
  public CharResponseWrapper(HttpServletResponse response){
    super(response);
    output = new CharArrayWriter();
  }   public PrintWriter getWriter(){
    return new PrintWriter(output);
  }
}

图14-3 显示了带有符合计数器的Duke书店的进入页面。

Duke's Bookstore

图14-3 Duke的书店

指定过滤器映射 

一个Web容器使用过滤器映射来决定如何将过滤器应用到Web资源。过滤器映射将过滤器与一个通过名称指定的Web组件或者URL模式的Web资源相匹配。过滤器按在WAR的过滤器映射列表中过滤器映射出现的次序调用。用户可以通过在Web应用部署描述符中直接编码的方法为WAR指定一个过滤器列表。

·  声明使用<filter>元素的过滤器。这个元素为过滤器生成一个名称,声明过滤器的实现类,并且初始化参数。

·  通过定义一个<filter-mapping>元素,将过滤器映射到Web资源。这个元素使用名称或URL模式将过滤器的名称映射到Web资源。

以下的元素显示了如何指定符合计数器和次序过滤器。为了定义过滤器,用户可以为过滤器,实现过滤器的类提供一个名称,以及其他一些初始化参数。

<filter>
  <filter-name>OrderFilter</filter-name>
  <filter-class>filters.OrderFilter</filter-class>
</filter>
<filter>
  <filter-name>HitCounterFilter</filter-name>
}
  <filter-class>filters.HitCounterFilter</filter-class>
</filter>

filter-mapping元素将按次序的过滤器映射到/receipt URL。映射也可以指定ReceiptServlet servlet。值得注意的是,filter、ilter-mapping、servlet和servlet-mapping元素必须按次序出现在Web应用部署描述符中。

<filter-mapping>
    <filter-name>OrderFilter</filter-name>
    <url-pattern>/receipt</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>HitCounterFilter</filter-name>
    <url-pattern>/enter</url-pattern>
</filter-mapping>

如果用户希望记录每个到Web应用的请求,就要将符合计数器过滤器映射到URL模式/*。表14-6为Duke书店应用程序总结了过滤器映射列表。过滤器用URL模式匹配,每个过滤器链只包含一个过滤器。

14-6 Duke书店过滤器映射列表

URL

过滤器

/进入

HitCounterFilter

/接收

OrderFilter

用户可将过滤器映射到一个或多个Web资源中,也可以映射多个过滤器到一个Web资源。如图14-4所示,过滤器F1被映射到servlet S1、S2和S3,过滤器F2被映射到servlet S2,而过滤器F3被映射到servlet S1和S2。

Filter to Servlet Mapping

图14-4 过滤器到Servlet的映射

回忆一下过滤器链被当作传递给一个过滤器的doFilter方法的一个对象的情况。这个链是通过过滤器映射间接生成的。过滤器在链中的次序与在Web应用部署描述符中出现的过滤器映射的次序相同。

当一个过滤器被映射到servlet S1时,Web容器调用F1的doFilter方法。在S1的过滤器链中每个过滤器的doFilter方法都将被链中前面的过滤器通过chain.doFilter方法调用。因为S1的过滤器链包含了F1和F3,F1到chain.doFilter的调用通过调用F3过滤器的doFilter方法实现。当F3的doFilter方法完成时,控制返回到F1的doFilter方法。

调用其他Web资源

Web组件有两种方法来调用其他的web资源:间接法和直接法。当Web组件将一个指向其他Web组件的URL嵌入到返回给客户端的内容中时,Web组件就间接调用其他Web资源。在Duke书店应用程序中,大多数的Web组件已经被嵌入到指向其他Web组件的URL中。例如,ShowCartServlet通过以嵌入的URL /bookstore1/catalog间接调用CatalogSersvlet。

Web组件也可以在执行时直接调用其他的资源。这有两种可能性:它可以包含其他资源的内容,或将请求传给其他资源。

为了调用正在运行Web组件的服务器上的资源,用户必须首先使用getRequestDispatcher("URL")方法获得对象RequestDispatcher

用户可以从请求或Web组件中获得RequestDispatcher对象。然而,这两种方法的行为有些不同。该方法将到被请求的资源的路径作为一个参数。一个请求可以获得一条相对路径(也就是说,它可以不从/开始),但是Web上下文要求一条绝对的路径。如果资源不存在,或者服务器没有为该类型的资源实现RequestDispatcher对象,getRequestDispatcher将返回空(null)。用户应该有处理这种情形的servlet。

在响应中包括其他资源

通常,在响应中包括其他Web资源非常有用。例如,在从Web组件返回的响应中包括题标(banner)内容或信息版权。为了包括其他资源,要调用RequestDispatcher object 的include方法。

include(request, response);

如果资源是静态的,include方法使包含服务器端的项目成为可能。如果资源是Web组件,该方法的效果是向内含的Web组件发送请求、执行Web组件,然后包括响应执行的结果。内含的Web组件可以访问请求对象,但是它只能局限于对响应对象的处理:

·  可以编写响应的主体并且提交响应。

·  不能设置头或调用任何方法(例如,setCookie ),以致影响响应头。

Duke书店应用程序的题标由BannerServlet生成。值得注意的是,因为BannerServlet将被发送到的servlet调用中的任一方法,因而执行doGet和doPost这两种方法。

public class BannerServlet extends HttpServlet {
  public void doGet (HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {

      PrintWriter out = response.getWriter();
    out.println("<body bgcolor=\"#ffffff\">" +
    "<center>" + "<hr> <br> &nbsp;" + "<h1>" +
    "<font size=\"+3\" color=\"#CC0066\">Duke's </font>" +
    <img src=\"" + request.getContextPath() +
    "/duke.books.gif\">" +
    "<font size=\"+3\" color=\"black\">Bookstore</font>" +
    "</h1>" + "</center>" + "<br> &nbsp; <hr> <br> ");
  }   public void doPost (HttpServletRequest request,
    HttpServletResponse response)     throws ServletException, IOException {

      PrintWriter out = response.getWriter();
    out.println("<body bgcolor=\"#ffffff\">" +
    "<center>" + "<hr> <br> &nbsp;" + "<h1>" +
    "<font size=\"+3\" color=\"#CC0066\">Duke's </font>" +
    <img src=\"" + request.getContextPath() +
    "/duke.books.gif\">" +
    "<font size=\"+3\" color=\"black\">Bookstore</font>" +
    "</h1>" + "</center>" + "<br> &nbsp; <hr> <br> ");
  }
}

Duke书店应用程序中的每个servlet通过以下代码包括BannerServlet产生的结果:

RequestDispatcher dispatcher =
  getServletContext().getRequestDispatcher("/banner");
if (dispatcher != null)
  dispatcher.include(request, response);
}  

将控制转移到另一个Web资源

在一些应用程序中,您可能希望由一个Web组件完成请求的预处理,而由另一个组件生成响应。例如,您可能想只处理请求的一部分,然后根据请求的属性将其传递给另一个组件。

为了控制另一个Web组件,用户要调用RequestDispatcher 的forward方法。当一个请求被转发时,请求URL被设置为发送页的路径。如果多个过程都要求得到原始的URL,可以将其作为请求属性存储起来。在JSP页面示例中描述的Duke书店应用程序使用Dispatcher servlet来存储原始URL中的路径信息,获取请求中的RequestDispatcher,然后发送到JSP 页template.jsp

public class Dispatcher extends HttpServlet {
  public void doGet(HttpServletRequest request,
    HttpServletResponse response) {
    request.setAttribute("selectedScreen",
      request.getServletPath());
    RequestDispatcher dispatcher = request.
      getRequestDispatcher("/template.jsp");
    if (dispatcher != null)
      dispatcher.forward(request, response);
  }
  public void doPost(HttpServletRequest request,
  ...
}

英国应该使用forward方法给出另一个资源,负责回复用户。如果已经访问了servlet内的ServletOutputStream或PrintWriter对象,就不能用这种方法,否则将产生一个IllegalStateException异常。

访问Web上下文

Web组件执行时所在的上下文是一个实现ServletContext接口的对象。用户可以使用getServletContext方法获取Web上下文。Web上下文提供访问以下资源的方法:

·       初始化参数

·       与Web上下文相关联的资源

·       对象属性

·       日志功能

Duke书店的过滤器filters.HitCounterFilter和 OrderFilter使用了Web上下文,这在过滤请求和响应中讨论过。过滤器存储了一个计数器作为上下文的属性。回忆一下控制共享资源的并发访问中提到的情况:计数器的访问方法被同步,以防止正并发运行的servlet出现不协调的操作。过滤器使用上下文的getAttribute方法来获取计数器对象,用上下文的log方法来记录计数器已递增的值。

public final class HitCounterFilter implements Filter {
 private FilterConfig filterConfig = null;
 public void doFilter(ServletRequest request,
   ServletResponse response, FilterChain chain)
   throws IOException, ServletException {
   ...
   StringWriter sw = new StringWriter();
   PrintWriter writer = new PrintWriter(sw);
   ServletContext context = filterConfig.
     getServletContext();
   Counter counter = (Counter)context.
     getAttribute("hitCounter");
   ...
   writer.println("The number of hits is: " +
     counter.incCounter());
   ...
   context.log(sw.getBuffer().toString());
   ...
 }
}

维护客户端状态  

很多应用程序都要求客户端发来一系列的请求,以便彼此联系。例如,Duke书店应用程序使用请求来存储用户购物车的状态。因为HTTP协议是无状态的,所以用基于Web的应用程序来维护这些状态,该应用程序被称为会话(session)。为了支持这些用来维护状态的应用程序,Java Servlet技术提供了一个API来管理会话 ,并且包含了几种实现会话的机制。

访问会话 

会话用HttpSession对象来表示。用户调用请求对象的getSession方法来访问会话。该方法返回一个与该请求相关的当前会话。如果这个请求没有会话,就创建一个会话。因为getSession可能修改响应的头部(如果使用了cookie作为会话的跟踪机制),这就要求在用户在获取PrintWriter 或ServletOutputStream之前调用请求。

将属性与会话相关联

用户可以通过名称将一个会话与对象属性关联起来。任何属于同一个Web上下文并且正在处理作为相同会话的一部分的Web组件都可以访问这些属性。

Duke书店应用程序将客户的购物车作为一个会话属性存储。这就允许将购物车存储在请求之间,并与servlet一起使用访问购物车。CatalogServlet为购物车添加项,ShowCartServlet用来显示购物车、删除购物车中的项和清理购物车, CashierServlet用来获取购物车中书的总价。

public class CashierServlet extends HttpServlet {
  public void doGet (HttpServletRequest request,
    HttpServletResponse response)
    throws ServletException, IOException {
      // Get the user's session and shopping cart
    HttpSession session = request.getSession();
    ShoppingCart cart =       (ShoppingCart)session.
        getAttribute("cart");
    ...
    // Determine the total price of the user's books
    double total = cart.getTotal();

通知与会话相关联的对象

回忆在“处理生命周期事件”中提到的情况:应用程序可以通知Web上下文和会话监听器对象servlet生命周期事件。您也可以通知一些与会话相关的事件的对象,例如:

·  当在会话中添加或删除一个对象时,为了接收这个通知,用户对象必须实现javax.http.HttpSessionBindingListener接口。

·  当对象所附着的会话将被阻塞或激活的时候。当会话在虚拟机之间移动、或在持久存储设备中保存和恢复时,它就被阻塞或者激活。为了接收这个通知,对象必须实现javax.http.HttpSessionActivationListener接口。

会话管理

因为没有办法通知HTTP客户端已经不再需要会话了,所以每个会话都带有一个超时(timeout)字段以便系统可以回收其资源。超时字段可以使用会话的[get|set]MaxInactiveInterval方法访问。用户也可以在部署描述符中设置超时字段:

<web-app>
   <display-name>Hello World Application</display-name>
   <description>A web application</description>
   <session-config>
      <session-timeout>60</session-timeout>
   </session-config>
</web-app>

为了确保一个有效的会话没有超时,用户应该通过服务方法周期性的访问会话,这样可以重新设置会话的存活时间(time-to-live)计数器。

当指定的客户端交互完成时,用户使用会话的invalidate方法结束服务器端的会话,并删除任何会话数据。

书店应用程序的ReceiptServlet是最后一个访问客户端会话的servlet,所以它负责结束会话。

public class ReceiptServlet extends HttpServlet {
  public void doPost(HttpServletRequest request,
          HttpServletResponse response)
          throws ServletException, IOException {
    // Get the user's session and shopping cart
    HttpSession session = request.getSession();
    // Payment received -- invalidate the session
    session.invalidate();
    ...

会话跟踪 

Web容器有几种方法使用户和会话相关联,这些方法都将在客户端和服务器之间传递一个标识符。这个标识符在客户端上作为一个cookie来维护,或者Web组件可以在每个返回到客户端的URL中包括标识符。

如果应用程序要使用会话对象,首先必须在客户端关闭cookies时让应用程序重写URL,以确保已经启用了会话跟踪。可以通过对servlet返回的所有URL调用响应的encodeURL(URL)方法来完成。只有在cookie被损坏时,该方法才包含会话ID(在URL中)。否则,它返回未改变的URL。

ShowCartServlet的doGet方法为位于购物车显示页最底端的三个URL编码,代码如下:

out.println("<p> &nbsp;
<p><strong><a href=\"" +
  response.encodeURL(request.getContextPath() + "/catalog") +
    "\">" + messages.getString("ContinueShopping") +
    "</a> &nbsp; &nbsp; &nbsp;" +
    "<a href=\"" +
  response.encodeURL(request.getContextPath() + "/cashier") +
    "\">" + messages.getString("Checkout") +
    "</a> &nbsp; &nbsp; &nbsp;" +
    "<a href=\"" +   response.encodeURL(request.getContextPath() +
    "/showcart?Clear=clear") +
    "\">" + messages.getString("ClearCart") +
    "</a></strong>");

如果cookie被禁止,会话将被编进URL(Check Out URL)中,如下:

http://localhost:8080/bookstore1/cashier;
  jsessionid=c0o7fszeb1

如果cookie已经启用,则URL就是简单的:

http://localhost:8080/bookstore1/cashier

结束Servlet

当servlet容器决定从服务中删除servlet时(例如,当容器希望回收存储器资源时,或在容器本身被关闭时),容器调用Servlet接口的destroy方法。在该方法中,用户将释放servlet使用的任何资源,并将任何永久状态存储。下面的destroy方法释放了由init方法(在“初始化servlet”初始化 Servlet有描述)创建的数据库对象。

public void destroy() {
 bookDB = null;
}

在servlet被删除时,所有的servlet服务方法都应该完成。在服务请求返回后,或在服务器指定的宽限期之后,无论哪一个先完成服务器都将通过调用destroy方法来完成确认。如果用户的servlet包含需要长时间运行的操作(也就是说,操作运行的时间可能超过宽限期),那么在调用destroy之后操作仍然可以继续执行。用户必须确认任何线程仍然在处理请求。这一节剩下的部分讨论如何:

·  对有多少线程现在正在运行service方法进行跟踪。

·  提供简洁的关闭方法。通过使用destroy方法通知长时间运行的线程关闭并等待其完成来实现。

·  使用长时间运行的方法周期性地轮询,以检查是否关闭。如果必要的话,停止其运行并对其进行整理,然后返回。

跟踪服务请求

为了跟踪服务请求,必须在servlet类中包括一个字段,该字段对正在运行的服务方法进行计数。该字段必须含有同步访问方法,用来递增、递减和返回其值。

public class ShutdownExample extends HttpServlet {
  private int serviceCounter = 0;
  ...
  //Access methods for serviceCounter
  protected synchronized void enteringServiceMethod() {
    serviceCounter++;
  }
  protected synchronized void leavingServiceMethod() {
    serviceCounter--;
  }
  protected synchronized int numServices() {
    return serviceCounter;
  }
}

service方法应该在每次使用方法时递增服务计数器,并且在每次方法返回的时候递减计数器。这是少数几次HttpServlet子类应该覆盖service方法的情况。新的方法应该调用super.service来保存所有原始的service方法的功能。

protected void service(HttpServletRequest req,
          HttpServletResponse resp)
          throws ServletException,IOException {
  enteringServiceMethod();
  try {
    super.service(req, resp);
  }
finally {
    leavingServiceMethod();
  }
}

通知方法关闭  

为了确保能简洁地关闭,destroy方法在所有的服务请求完成之前不能释放任何共享资源。这样做一部分是为了检查服务计数器,另一部分则是通知长时间运行的方法是时候关闭了。要进行这样的通知,需要使用另一个字段。该字段有一种通常的访问方法:

public class ShutdownExample extends HttpServlet {
  private boolean shuttingDown;
  ...   
//Access methods for shuttingDown
  protected synchronized void setShuttingDown(boolean flag) {
    shuttingDown = flag;
  }   protected synchronized boolean isShuttingDown() {
    return shuttingDown;
  }
}

一个使用这些字段的destroy方法的例子提供了简洁的关闭方法,如下:

public void destroy() {
  /* Check to see whether there are still service methods /*
  /* running, and if there are, tell them to stop. */
  if (numServices() > 0) {
    setShuttingDown(true);
  }
    /* Wait for the service methods to stop. */
  while(numServices() > 0) {
    try {
      Thread.sleep(interval);
    } catch (InterruptedException e) {
    }
  }
}  

 

创建友好的长时间运行的方法

要提供一种简洁的关闭方法,最后一步是使所有长时间运行的方法行为友好。可能长时间运行的方法应该检查用来通知其关闭的字段的值。如果必要的话,可以中断其运行。

public void doPost(...) {
  ...
  for(i = 0; ((i < lotsOfStuffToDo) &&
    !isShuttingDown()); i++) {
    try {
      partOfLongRunningOperation(i);
    } catch (InterruptedException e) {
      ...
    }
  }
}

更多的信息

为了了解Java Servlet技术更多的信息,请查看:

·  Web网址资源列表http://java.sun.com/products/servlet.

·  Java Servlet 2.3 Specification.

 

 

 

常见问答

下载中心

产品简介

 

 

Solaris论坛