為了理解問題的根源並適當地響應,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<n; i++) {
System.err.println(elements[i].getFileName() + ":" +
elements[i].getLineNumber() + " ==> " +
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. 樣本日志文件輸出<?xml version="1.0" standalone="no"?>
<!DOCTYPE log SYSTEM "logger.dtd">
<log>
<record>
<date>2001-10-30T16:24:23</date>
<millis>1004563463843</millis>
<sequence>0</sequence>
<logger>net.zukowski.ibm</logger>
<level>WARNING</level>
<class>Exceptions</class>
<method>main</method>
<thread>10</thread>
<message>arrayAccess</message>
</record>
<record>
<date>2001-10-30T16:24:24</date>
<millis>1004563464015</millis>
<sequence>1</sequence>
<logger>net.zukowski.ibm</logger>
<level>WARNING</level>
<class>Exceptions</class>
<method>main</method>
<thread>10</thread>
<message>main</message>
</record>
</log>
完整的示例
清單 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<n; i++) {
System.err.println(elements[i].getFileName() + ":" +
elements[i].getLineNumber() + " ==> " +
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");
}
}
}
}