为了理解问题的根源并适当地响应,Merlin 发行版添加了几个与异常处理相关的功能。现在,您不必手工分析堆栈转储信息就可以检查堆栈跟踪信息,并且可以把异常连成一条菊花链,这样就能够在重新抛出异常时附加上异常的原因,这会大大促进调试工作。此外,现在还有一个内建的日志记录工具用来记录消息的不同级别。在 Merlin 的魔力系列的这一部分,John Zukowski 演示了这些新的日志记录和异常功能的工作原理并提供了一个示例程序以供查看和下载。
这个 Merlin 发行版中新添加的许多功能(比如异常处理和日志记录功能),并不象其它一些功能一样显著或令人兴奋,但它们很有用,值得我们关注。所有的 Java 开发者应该都熟悉执行异常处理的基本结构:把可能抛出异常的代码放在 try 块中,然后,万一在这个块中确实抛出了异常,则由这个块下面的 catch 子句来处理。在这个 Merlin 发行版中,这个基本结构并没有发生改变。发行版 1.4 中的新功能是如果从 catch 子句重新抛出了一个异常,您可以附加上该异常的初始原因。这真是一个便于调试的高招!并且,如果您想记录下异常发生在何处,您不必手工分析堆栈跟踪信息。现在支持通过程序的方式访问堆栈跟踪数据,还有一个“日志记录 API”(Logging API)用于记录这些数据(或其它任何内容)。
下面是这个月我们要讨论的新功能的列表:
链式异常工具
以程序的方式访问堆栈跟踪信息
日志记录 API
开始
清单 1 中的基本程序包含三个方法,这三个方法都可抛出异常。每个异常情况通过显示一条消息来处理。在第一个例子中,异常被重新抛出以便显示针对该问题的第二条消息。
清单 1. 异常处理的骨架程序import java.io.*;public class Exceptions { private static void fileAccess() throws IOException { // Fails because prefix is too short File f = File.createTempFile("x", "y"); } private static void divZero() { System.out.println(1/0); } private static void arrayAccess(String array[]) { System.out.println("First: " + array[0]); } public static void main(String args[]) { try { try { fileAccess(); } catch (Exception e) { System.err.println("Prefix too short"); throw e; } } catch (Exception cause) { System.err.println("Cause: " + cause); } try { divZero(); } catch (Exception e) { System.err.println("Division by Zero"); e.printStackTrace(); } try { arrayAccess(args); } catch (Exception e) { System.err.println("No command line args"); } }}
链式异常
一个链式异常是这样一种异常,它允许您将一个“成因”异常附加到正在抛出的异常。本质上,您是在创建一条异常菊花链。例如,当抛出(和捕获)您的定制异常时,您可以说导致该异常的原因是一个 I/O 异常。支持链式异常是从 java.lang.Throwable 类开始的。现在,不是只有两个构造函数(其中一个无参数,另一个接受一条详细消息作为参数),而是有四个构造函数:
Throwable()
Throwable(String message)
Throwable(Throwable cause)
Throwable(String message, Throwable cause)
当创建自己的异常类型时,您还应该另外添加两个构造函数。那样,您就可以在异常被创建时,很容易地传递产生该异常的原因。即使不改变您的 Exception 子类,您仍然可以将它们链接起来。这个方法只要求您调用自己子类的 initCause(Throwable cause) 方法。
要演示链接,清单 2 应该替换 Exceptions 类中的前两个 try 块。它定义一个定制异常类(同时附加上原因),并抛出这个定制异常类而不是 fileAccess() 异常处理的初识异常:
清单 2. 链式异常代码try { try { fileAccess(); } catch (Exception e) { class TheException extends Exception { public TheException() { } public TheException(String message) { super(message); } public TheException(Throwable throwable) { super(throwable); } public TheException(String message, Throwable throwable) { super(message, throwable); } } TheException theExc = new TheException("Prefix too short", e); throw theExc; }} catch (Exception cause) { System.err.println("Cause: " + cause); System.err.println("OriginalCause: " + cause.getCause());}
堆栈访问
现在,我们将通过访问异常的堆栈跟踪信息来增加一点复杂性。如清单 2 中第二个方法调用所示,可以调用 printStackTrace() 来显示通向抛出异常的代码行的调用序列的堆栈转储信息。 printStackTrace() 方法可以接受 PrintStream 或 PrintWriter 作为参数,并且如果不为该方法提供目的地的话,它将把输出发送到 System.err 。
如果您希望以自己的格式来显示堆栈跟踪信息,而不是以缺省的格式来转储,您可以调用 getStackTrace() 方法,该方法将返回一个 StackTraceElement 对象数组。您可以发现每个元素的许多不同的功能:
getClassName()
getFileName()
getLineNumber()
getMethodName()
isNativeMethod()
通过调用每个元素的不同方法,您可以用任何自己喜欢的格式来显示堆栈转储信息。用清单 3 取代 printStackTrace() 调用将会使程序显示每个堆栈元素的文件名、行号和方法名称。
清单 3. 手工显示堆栈跟踪信息StackTraceElement elements[] = e.getStackTrace();for (int i=0, n=elements.length; i " + elements[i].getMethodName()+'()");}
有一点要注意:数组的第一个元素(而不是最后一个元素)是这个调用跟踪的栈顶。
日志记录
不想把堆栈跟踪信息发送到 System.err ,您可以使用新的 java.util.logging 包中提供的 Java 平台的日志记录工具。虽然通过 XML 和过滤有许多配置选项可用,但基本结构还是要求获得一个 Logger 对象并使用一般的 log(Level level, String message) 方法对方法进行日志记录,或调用针对特别日志级别的方法(如 fine() )。有七个不同的级别,另加两个指明“全部”或“无”的级别:
SEVERE
WARNING
INFO
CONFIG
FINE
FINER
FINEST
ALL
NONE
清单 4 向第三个 try 块添加了日志记录功能,只记录堆栈跟踪每部分的方法名称。
清单 4. 基本日志记录器用法System.err.println("No command line args");Logger logger = Logger.getLogger("net.zukowski.ibm");StackTraceElement elements[] = e.getStackTrace();for (int i=0, n=elements.length; i<n; i++) { logger.log(Level.WARNING, elements[i].getMethodName());}
缺省情况下,日志记录消息被发送到控制台。可以通过为 LogManager 附加一个 Handler 将日志记录添加到文件,如清单 5 所示。
清单 5. 将日志记录添加到文件try { LogManager manager = LogManager.getLogManager(); Handler handler = new FileHandler("zuk.log"); manager.addGlobalHandler(handler); // log it} catch (IOException logException) { System.err.println("Logging error");}
当控制台输出格式不是任何一种易于分析的格式时,文件输出存储为 XML 文档。清单 6 显示了这个示例的这种输出。
清单 6. 样本日志文件输出 2001-10-30T16:24:23 1004563463843 0 net.zukowski.ibm WARNING Exceptions main 10 arrayAccess 2001-10-30T16:24:24 1004563464015 1 net.zukowski.ibm WARNING Exceptions main 10 main
完整的示例
清单 7 提供了一个完整的示例以供您试验这些新功能。
清单 7. 完整的示例import java.io.*;import java.util.logging.*;public class Exceptions { private static void fileAccess() throws IOException { // Fails because prefix is too short File f = File.createTempFile("x", "y"); } private static void divZero() { System.out.println(1/0); } private static void arrayAccess(String array[]) { System.out.println("First: " + array[0]); } public static void main(String args[]) { try { try { fileAccess(); } catch (Exception e) { class TheException extends Exception { public TheException() { } public TheException(String message) { super(message); } public TheException(Throwable throwable) { super(throwable); } public TheException(String message, Throwable throwable) { super(message, throwable); } } TheException theExc = new TheException("Prefix too short", e); throw theExc; } } catch (Exception cause) { System.err.println("Cause: " + cause); System.err.println("OriginalCause: " + cause.getCause()); } try { divZero(); } catch (Exception e) { System.err.println("Division by Zero"); StackTraceElement elements[] = e.getStackTrace(); for (int i=0, n=elements.length; i " + elements[i].getMethodName()+'()"); } } try { arrayAccess(args); } catch (Exception e) { System.err.println("No command line args"); try { LogManager manager = LogManager.getLogManager(); Handler handler = new FileHandler("zuk.log"); manager.addGlobalHandler(handler); Logger logger = Logger.getLogger("net.zukowski.ibm"); StackTraceElement elements[] = e.getStackTrace(); for (int i=0, n=elements.length; i<n; i++) { logger.log(Level.WARNING, elements[i].getMethodName()); } } catch (IOException logException) { System.err.println("Logging error"); } } }}
与其在那里苦苦挣扎,碍于面子硬撑,倒不如微笑着面对,