自定义标记中的错误处理
  

2003 年 9 月 29 日

在自定义标记中引用更多交互性的同时,也会增加错误出现的可能性,尤其是以非法参数形式出现的错误。在本期的 JSP最佳实践中,Brett McLaughlin 向您展示了如何在源代码中捕获和忽略 IllegalArgumentException 异常。

在我们讨论 JSP 自定义标记的时候,我们一直都设法回避了错误处理的重要细节。在我们已经推出的 JSP 最佳实践系列的前面一半的文章中,大多数讲的都是 JSP 容器提供的功能,比如说 paramout import 标记。在这些情况下,错误处理是 JSP 规范和 Web 服务器实现的事,也就是说就算是我们想做什么也做不了。

但是最近两期我们主要讨论了自定义标记库和属性。我们不仅开始往 JSP 页面中添加新的功能,而且还创建了允许 JSP 作者与代码交互的属性。而且,很可能我们已经在 JSP 页面中引入了新型的错误,或者由于(以后什么时候)页面的作者的过失可能会引起标记的代码或者后面的输出产生灾难性的后果。无论在哪一种情况下,往自定义标记中加入错误处理机制就变得很有必要。

有关例子的说明:在本期 JSP最佳实践中我们使用的所有例子的代码都是建立在前一期开发的代码的基础上的。在继续阅读本文之前,您应该确保已经成功地完成了上一期的设置和编码练习。

回顾

我们首先将重复以前的步骤,回顾一下我们在上一期开发的一些代码,但是这次我们将着重发现代码中的错误(或者潜在的错误可能性)。

首先,我们先回忆一下向 lastModified 标记中添加的新属性 format 。这个属性允许页面作者传入一个格式字符串,java.text.SimpleDateFomrat 类可以使用该字符串来定制最后修改日期戳的格式。虽然这种交互对于大部分 JSP 页面是必不可少的,但是它也给我们的标记引入了潜在的错误可能性。

例如,一个页面作者可以轻易地提供一个格式字符串,这个字符串就可能导致一个讨厌的错误消息。如果您想看看得到什么样的消息,可以按照清单1的样子修改您的 footer 文件(或者任何其它使用 lastModified 标记的 JSP 页面):



清单1. 一个无效的格式字符串
<%@ taglib prefix="site-utils"
            uri="http://www.newInstance.com/taglibs/site-utils"%>
         </td>
         <td width="16" align="left" valign="top"> </td>
   </tr>
   <!-- End main content -->
<!-- Begin footer section -->
   <tr>
     <td width="91" align="left" valign="top" bgcolor="#330066"> </td>
     <td align="left" valign="top"> </td>
     <td class="footer" align="left" valign="top"><div align="center"><br>
         &copy; 2003 
         <a href="mailto:webmaster@newInstance.com">Brett McLaughlin</a><br>
         Last Updated: <site-utils:lastModified 
		 format="PHH:mm a, MM/dd/yyyy"/>
       </div></td>
         <td align="left" valign="top"> </td>
     <td width="141" align="right" valign="top" bgcolor="#330066"> </td>
   </tr>
</table>
<!-- End footer section -->
          





一个无效参数的例子

清单1中格式字符串“PHH:mm a, MM/dd/yyyy”中的“P”位置放错了,结果导致一个无效的格式字符串参数。在一个 Java 类中,这种类型的错误会导致抛出一个 IllegalArgumentException 异常。然而,在 JSP 环境中,错误将会传给最终用户,导致类似 这里所示的错误消息。

对于页面作者来说,收到和处理这类错误消息是一件非常糟糕的事情。如果错误消息绕过页面作者直接传给最终用户,情况会更糟糕。在最坏的情况下,用户会被站点出现的错误消息吓走并永远不再回来,从而导致站点的知名度、收入和信誉下降――而所有这些对于任何一家公司来说又都是非常重要的。





忽略无效的参数

幸运的是,我们可以通过在自定义标记中提供一些基本的错误处理功能来处理这类错误。最简单的错误处理方式是定义默认(或者回撤)行为来忽略无效的参数。在 lastModified 标记的例子中,我们已经为这种情况创建了错误处理机制的第一部分,方法是为时间戳提供一个默认的格式。然而,最好是将默认值定义为一个常量,然后引用该常量而不要直接将默认字符串赋值给变量。清单2说明了 DEFAULT_FORMAT 常量是怎样添加到程序中,然后再赋值给 format 变量的:



清单2. 为 LastModifiedTag 置一个默认格式
package com.newInstance.site.tags;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.TagSupport;
public class LastModifiedTag extends TagSupport {
    private final String DEFAULT_FORMAT = "MMM d, yyyy";
    private String format = DEFAULT_FORMAT;
    public void setFormat(String format) {
      this.format = format;
    }
    // doEndTag() method, as seen in previous tips
}
          

当然,这个默认值可以一直保持不变,也可以通过添加新的格式字符串来修改。如果页面作者用了一个无效的格式字符串,他就会接收到一个错误消息。所以说,除了提供默认行为和一个修改那个行为的机制以外,我们还需要提供一个在使用了无效的格式字符串之后回到默认行为的机制。但是在我们能够修正一个错误之前,需要先捕获它。





Try、catch 和 fix

在任何类型的错误处理中,我们都应该在错误的来源处捕获错误,所谓错误的来源是指任何第一次出现问题的地方。在这里,无效的格式字符串将会被传给 SimpleDataFormat 构造方法,从而导致抛出一个 IllegalArgumentException 异常。我们把 try/catch 代码块放到构造方法中,如清单3所示:



清单3. 用于处理无效格式字符串的 try/catch 代码块
package com.newInstance.site.tags;
import java.io.File;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.tagext.TagSupport;
public class LastModifiedTag extends TagSupport {
    private final String DEFAULT_FORMAT = "MMM d, yyyy";
    private String format = DEFAULT_FORMAT;
    public void setFormat(String format) {
      this.format = format;
    }
    public int doEndTag() {
      try {
        HttpServletRequest request = 
          (HttpServletRequest)pageContext.getRequest();
        String path = pageContext.getServletContext().getRealPath(
          request.getServletPath());
        File file = new File(path);
        DateFormat formatter;
        try {
          formatter = new SimpleDateFormat(format);
        } catch (IllegalArgumentException e) {
          formatter = new SimpleDateFormat(DEFAULT_FORMAT);
        }
        pageContext.getOut().println(
          formatter.format(new Date(file.lastModified())));
      } catch (IOException ignored) { }
      return EVAL_PAGE;
    }
}
          

注意, lastModified 输出现在回到了有 IllegalArgumentException 异常抛出时的默认字符串。测试这个输出的方法如下,使用新代码重新编译标记,然后重新启动 servlet 容器,再使用一个无效的日期格式访问页面。这次没有出现讨厌的栈跟踪,您可以看到正常显示的页脚,它以 DEFAULT_FORMAT 所指定的格式显示最后修改日期和时间。





结束语

希望这一期的提示证明了在自定义标记中包含错误处理机制的重要性。每个新的自定义标记(和属性)都代表一个惟一的 JSP 平台的扩展,所以对于可能由这个扩展所引起的每一个问题,都应该提供一个惟一解决方案。

要跟着练习在这里学习的内容,可以重写前面写过的自定义标记库。现在再回头看一下,您可能会发现至少有一个或者两个错误情况需要改正。记住您在这里学到的简单的try-catch-fix 方法:定位错误的来源,应用某种机制捕获它,然后提供某种行为忽略它。

在下一期的 JSP最佳实践中,我们将完成自定义标记库的学习,转而讨论打包(packaging)。您将了解到怎么将一个或多个自定义标记库打包到一个 JAR 文件中,以便于维护、发布以及 Web 容器的安装。到那时,我们网上见。

共8页 上一页 下一页