1 引言
傳統(tǒng)的基于函數(shù)返回錯誤代碼的錯誤處理方法存在明顯的不足,如程序邏輯復雜,,結構不清,;可靠性差,,維護不便;返回信息有限,,不直觀需譯碼;返回代碼標準化困難,,代碼復用率低,;在面向對象的應用系統(tǒng)中,有些如構造方法等沒有返回值而無法報告程序錯誤,。因此,,在Java中新的錯誤檢測和報告方法—異常處理機制應運而生。
異常是指中斷程序正常執(zhí)行流程的錯誤事件,,如程序打開不存在的文件,、裝載不存在的類、網(wǎng)絡連接中斷,、被零除,、訪問數(shù)組越界、系統(tǒng)資源耗盡等,。在Java中,,異常[1](Exception,例外)是特殊的運行錯誤對象,,是異常類的一個對象,,而每個異常類代表一種運行錯誤,在異常類中封裝了該運行錯誤的信息和處理錯誤的方法等內容,。Java異常處理機制的基本思想是由發(fā)現(xiàn)而不能處理錯誤的方法引發(fā)一個異常對象,,然后由該方法的直接或間接調用者捕獲并處理這個錯誤。其優(yōu)越性有:在catch中傳播與捕獲錯誤信息,,實現(xiàn)了錯誤代碼與業(yè)務邏輯的分離,,結構清晰;可對錯誤類型分組并標準化,;方便了對錯誤的定位與維護,;能有效防止由于異常而導致程序運行崩潰,可靠性高,;強制程序員考慮程序的容錯性,、健壯性和安全性,。
2? Java異常處理機制
2.1? 異常類
2.1.1? 系統(tǒng)定義的異常類
異常類用于處理異常,分為系統(tǒng)定義的異常類和用戶自定義的異常類[1],。在java.lang包中提供的Throwable類是異常類層次結構的頂層類,,Error類和Exception類是從Throwable類直接派生的兩個知名子類。
Exception類:它代表了Java語言中異常的基本屬性,,除Java預定義的由Exception類派生的諸多異常類外,,還支持用戶擴展Exception類來實現(xiàn)自定義異常類。其構造函數(shù)主要有public Exception()和public Exception(String s)等,;它從Throwable類繼承了若干方法,,常用的有:public String toString()方法,用來返回異常類信息,;public void printStackTrace()方法,,默認在當前標準輸出設備上輸出當前異常對象的堆棧使用軌跡。Exception類定義的是非致命性錯誤,,允許用戶編寫代碼來處理這類錯誤,,并繼續(xù)程序的執(zhí)行。通常觸發(fā)異常(Exception)的原因有打開的文件不存在,;網(wǎng)絡連接中斷,;操作數(shù)超過允許范圍;想要加載的類文件不存在,;試圖通過空的引用型變量訪問對象,;數(shù)組下標越界等。
Error類:它定義的錯誤是致命性錯誤,,如虛擬機錯誤,、裝載錯誤、動態(tài)連接錯誤,,一般會導致程序停止執(zhí)行,,通常是由于Java系統(tǒng)或執(zhí)行環(huán)境發(fā)生錯誤(Error)而導致的。由于這類異常主要與硬件,、運行時系統(tǒng)有關,,而不是由用戶程序本身拋出的,因此用戶程序不對這類異常進行處理,。
需指出,,除java.lang包中定義的異常處理之外,其他的Java包中也包括異常,。實際上幾乎每個Java包都定義了相應的異常類,。此外,運行時異常RuntimeException類及其派生子類是Java程序員不用處理的異常,。Java創(chuàng)建者認為運行時異常不應由程序來處理,,而且程序也很難真正的對付運行時異常。
2.1.2? 用戶自定義異常類
用戶自定義異常類是指擴展Exception類或其他某個已經存在的系統(tǒng)異常類或其他用戶異常類而形成新的異常類,??梢越o新的異常類定義新的屬性和方法,或重載父類的屬性和方法,,并使這些屬性和方法能夠體現(xiàn)該類所對應的錯誤信息,。要特別注意的是:第一? 一個方法被覆蓋時,覆蓋的方法必須扔出與被覆蓋方法相同的異?;蚱洚惓n惖淖宇?;第二? 若父類拋出多個異常,則覆蓋方法只能拋出父類所拋出的異常的一個子集,,或者說不能拋出新的異常,。
2.2? 基本機制與語法結構
2.2.1? 基本機制
Java異常處理機制采用中斷模式[2],即引發(fā)并拋出異常后,,中止正在執(zhí)行的程序塊,,控制流轉至異常處理器,待完成異常處理后,,再返回調用點繼續(xù)執(zhí)行,。異常處理的基本算法是:
Step1:拋出異常,即創(chuàng)建一個異常對象并將它交給運行時系統(tǒng)的過程,;
Step2:捕獲異常,,即找到異常處理程序的過程:運行時系統(tǒng)從發(fā)生錯誤的方法開始回溯,在方法調用堆棧里向后搜索,,直到找到能處理當前發(fā)生的異常的處理程序的方法,;
Step3:處理異常,即通過方法調用來實現(xiàn)對異常的處理,;
Step4:終止異常處理,。若運行時系統(tǒng)在方法調用棧中遍歷了所有的方法而未找到合適的異常處理程序,則顯示缺省錯誤并終止執(zhí)行運行時系統(tǒng)的異常處理,。
2.2.2? 語法結構
Java異常處理機制通過throws,、throw、try,、catch和finally 5個關鍵詞來實現(xiàn),,分為三個基本部分[3]。
·throws:此關鍵字統(tǒng)一定制并明確標明了一個方法可能拋出的各種異常,,這些異常是該方法定義的一部分,。其實質是允許將異常處理遞歸交給調用它的上一級方法去處理,此時Java編譯器會強制此方法的調用者必須將其放在調用方法的try、catch塊中以拋出并捕獲處理這些異常,。
·throw:此語句用來拋出緊跟其后的一個異常對象給此方法的調用者,,此異常對象可用new創(chuàng)建,或者是一個Throwable的實例句柄通過參數(shù)傳到catch中,。因為用戶自定義的異常不能由系統(tǒng)自動拋出,,所以必須借助于throw語句來拋出各種錯誤情況所對應的異常,且要求在程序中的合適位置定義好用戶自定義的異常類,。
·try-catch-finally:此語句是Java錯誤處理的基本結構,,主要用來捕獲和處理一個或多個異常。通常由try,、catch,、finally三個塊組成。?。﹖ry塊:將所有可能拋出異常的代碼部分放入try塊中,;ⅱ)catch塊:用緊跟在try塊后面一個或多個catch子句來捕獲異常,其的目標是處理異常,,把變量設到合理的狀態(tài),,并象沒有出錯一樣繼續(xù)運行。若一個子程序不處理這個異常,,則可返回到上一級處理,,如此不斷的遞歸向上直到最外一級。ⅲ)finally塊:finally是Java異常處理機制的精髓,,使用finally可以維護對象的內部狀態(tài),、清理非內存資源、將系統(tǒng)恢復到應該處于的狀態(tài),。若沒有finally,,要實現(xiàn)其功能的代碼是很費解的。finally塊是可選塊,,若定義了finally塊,,則不論try塊中有無異常產生,finally塊都會被執(zhí)行,;甚至若在try或catch塊中執(zhí)行了return,、break語句,finally塊也會被執(zhí)行,,但要特別注意此時finally塊后面的語句并不會被執(zhí)行,。只有在try或catch中執(zhí)行了System.exit(0)操作,才不會執(zhí)行finally塊,。另要特別指出的是:捕獲異常時,,catch語句是按其位置由前至后依次對被拋出的異常對象進行匹配捕獲,,若有多個catch語句,則異常類要按從子類到父類的順序放置,;在應用技巧中,,還可通過在try塊中由throw拋出“異常”,,然后在catch塊中捕獲之,,實現(xiàn)程序中業(yè)務邏輯控制流程的跳轉,。
2.3? 異常處理的基本原則
對于非運行時異常必須捕獲或聲明,,而對運行時異常則不必,可以交給Java運行時系統(tǒng)來處理,;對于自定義的異常類,,通常把它作為Exception類的子類,且類名常以Exception結尾,,不要把自定義的異常類作為RuntimeException類或Error的類的子類,;在捕獲或聲明異常時,要選取合適類型的異常類,,注意異常類的層次,,根據(jù)不同的情況使用一般或特殊的異常類;根據(jù)具體的情況選擇在何處處理異常,,或者在方法內捕獲并處理,,或者用throws子句把它交給調用棧中上層的方法去處理;在catch語句中盡可能指定具體的異常類型,,必要時使用多個catch,;使用finally語句為異常處理提供統(tǒng)一的出口;若無法處理某個異常,,則不捕獲它,;若捕獲了一個異常,則要對它進行適當?shù)奶幚?;盡量在靠近異常被拋出的地方捕獲異常,;除非要向上層遞歸拋出異常,否則要在捕獲異常的地方將其記錄到日志中,。
3? EJB中的異常處理
3.1? EJB異常處理
EJB(Enterprise JavaBean)是J2EE企業(yè)級應用開發(fā)的核心組件,,EJB的分布式和事務屬性使得其異常處理成為一個更重要的問題[4]。EJB中異??煞譃槿怺5]:?。㎎VM異常:由JVM拋出,是一種致命錯誤,。ⅱ)系統(tǒng)異常:一般由JVM以RuntimeException的子類拋出,,是一種非受查異常。ⅲ)應用程序異常:它是一種定制異常,由應用程序或第三方類庫以Exception類或其子類拋出,,是一種受查異常,,通常由EJB方法的調用者來處理之。EJB容器攔截了EJB組件上的每一個方法調用,,因此方法調用中發(fā)生的每一個異常也被EJB容器所攔截,。EJB規(guī)范只處理應用程序異常和系統(tǒng)異常這兩種類型的異常。
應用程序異常:是指在遠程接口的方法說明中所聲明的異常,,它不是遠程異常RemoteException,,也不應繼承RuntimeException或其子類。但是,,在遠程接口方法的throws子句中聲明的非受查異常并不會被當作應用程序異常,。應用程序異常是業(yè)務工作流中的例外,當這種類型的異常被拋出時,,客戶機可得到一個恢復選項,。當發(fā)生應用程序異常時,默認情況下EJB容器不會自動回滾事務,,只有被顯式說明并通過調用關聯(lián)的EJBContext對象的setRollbackOnly()方法才能回滾事務,。實際上,對于應用程序異常EJB容器通常以它原本的狀態(tài)傳送給客戶機而不會以任何方式包裝或修改它,。
系統(tǒng)異常:通常被定義為非受查異常,,EJB方法不能從這種異常中恢復。當EJB容器攔截到非受查異常時,,會自動回滾事務并執(zhí)行必要的清理工作,,然后把該非受查異常包裝到RemoteException類或其子類中并拋給客戶機。對于受查異常,,若要使用EJB容器的內務處理,,則必須將受查異常作為非受查異常拋出。因此,,每當觸發(fā)受查系統(tǒng)異常時,,應該對其原始的異常以javax.ejb.EJBException類或其子類方式包裝后拋出。由于EJBException本身是非受查異常,,因此不需要在方法的throws子句中聲明它,。EJB容器會自動捕獲EJBException或其子類,并把它包裝到RemoteException中,,然后拋給客戶機,。
需指出,雖然EJB規(guī)范規(guī)定系統(tǒng)異常由應用程序服務器記錄,,但記錄格式會因應用程序服務器的不同而異,。為確保異常記錄格式的統(tǒng)一,,方便對其進行統(tǒng)計分析,在代碼中記錄異常是一種好的策略,。此外,,在EJB1.0規(guī)范中要求把受查系統(tǒng)異常以RemoteException拋出,而EJB 1.1及以上規(guī)范則規(guī)定EJB實現(xiàn)類絕不應拋出RemoteException,。
3.2? 關鍵技術
3.2.1? 日志機制
盡管用System.out.println()方法跟蹤異常方便,,但開銷大,對I/O處理的同步控制將大大降低系統(tǒng)吞吐量,。缺省時堆棧跟蹤被輸出至控制臺,,但在實際的應用系統(tǒng)中,通過瀏覽控制臺來查看異常跟蹤不太現(xiàn)實,。因此,,在大型應用系統(tǒng)的開發(fā),、測試和運行等生命周期中,,采用日志機制和恰當?shù)木幋a策略,精確地記錄各種類型的異常,,可以降低系統(tǒng)開銷,,提高軟件性能和質量。知名的日志實用程序有兩種:Log4J[6]是Apache的Jakarta的一個開放源代碼的項目,,J2SE 1.4捆綁提供了日志處理包(java.util.logging)[7],,它們的使用方法請參考相關文獻。
3.2.2? Decorators設計模式
在面向對象的程序設計中若用一個對象(the Decorators)包裝另外一個對象,,被稱為Decorators設計模式,。基于Decorators設計模式,,通過包裝原始的異常消息并在EJB組件中將其重新拋出,,以方便對該異常的查詢和訪問。其次,,由于Log4J只能記錄String消息,,所以要利用Decorators設計模式定義一個專門類負責把堆棧跟蹤轉換成String,以獲取該堆棧跟蹤的String表示,。
3.3? EJB異常處理策略
3.3.1? 常見EJB異常處理的不足
·拋出帶有出錯消息的異常:此種方法存在異常內容可能被“吞掉”的現(xiàn)象,。
·記錄到控制臺并拋出一個異常:僅當控制臺可用時調用者才能向后跟蹤。
·包裝原始的異常以保護其內容:可能導致重復記錄,,產品日志或控制臺不能被交叉引用,。
3.3.2? EJB異常處理的優(yōu)化策略
·優(yōu)化應用程序異常處理:由EJB開發(fā)者顯式拋出,通常包裝了含義清楚消息,,不必將其記錄到EJB層或客戶機層,,而以一種直觀有意義的方式提供給最終用戶,,并帶上其解決方案的途徑。
實體Bean一般是啞類,,通常應用程序異常主要來源于會話Bean,。從實體Bean拋出的應用程序異常類型通常是CreateException、FinderException,、RemoveException及它們的子類,。當引用其它EJB遠程接口時,實體Bean會遇到RemoteException,,在查找其它EJB組件時會遇到NamingException,,若使用BMP實體Bean,則會碰到SQLException,。與這些類似的受查系統(tǒng)異常應該被捕獲,,并被包裝起來,作為EJBException或它的一個子類拋出,。在優(yōu)化的EJB設計中,,客戶機一般不直接調用實體Bean上的方法,而通過會話Bean間接訪問實體Bean,。若會話Bean調用相同的實體Bean方法,,則要避免對異常的重復記錄。會話Bean和實體Bean處理系統(tǒng)異常的方式基本相似,??刹捎迷L問標識技術避免對同一異常的重復記錄。
·優(yōu)化系統(tǒng)異常處理:比應用程序異常處理更為復雜,,它的發(fā)生不受EJB開發(fā)者的控制且異常信息不直觀,,需要對其原始異常進行包裝,以清楚地表達系統(tǒng)異常的含義,。
·優(yōu)化Web層設計:通常把異常記錄到Web層本身,,則易于實現(xiàn)且代碼簡潔。這要求Web層必須是EJB層的唯一客戶機,,且Web層必須建立在業(yè)務委派(Business Delegate),、FrontController或攔截過濾器(Intercepting Filter)等模式和Struts或其它類似于MVC的框架基礎上。
實際應用中,,即使采用良好的異常處理策略,,但由于編譯器和運行時優(yōu)化,會限制使用堆棧跟蹤程序跟蹤異常的能力,。通常把大的方法調用分割為更小的,、更易于管理的塊,并提高代碼復用率,;并將異常類型按需要劃分成細粒度的,、具體的異常,,在捕獲異常時則捕獲已規(guī)定好的具體類型的異常,避免捕獲所有類型的異常,。
3.4? EJB異常處理實例
例1:ejbCreate()方法中的FinderException異常處理,。其代碼如下:
public Object ejbCreate(RatepayingOrderValue value) throws CreateException {
try { if (value.getItemName() == null) {
throw new CreateException("不能創(chuàng)建報稅單!"); }
String taxpId = value.getTaxpayerId();
Taxpayer taxp = taxpayerHome.fingByPrimaryKey(taxpId);
this.taxpayer = taxp;
} catch (FinderException fe) {
//作為應用程序異常,,還是系統(tǒng)異常,?
} catch (RemoteException re) {
//這是系統(tǒng)異常,并記錄在日志中,。
throw ExceptionLogUtil.createLoggableEJBException(re);
}
return null;
}
例1中報稅單RatepayingOrder實體Bean的ejbCreate()方法試圖獲取納稅人Taxpayer實體Bean的一個遠程引用,,可能導致FinderException。此處,,盡管可把FinderException當作應用程序異?;蜃飨到y(tǒng)異常,但是若把它當系統(tǒng)異常則更好,,因為這可以提高EJB組件對客戶機的透明性,。同理,對于會話Bean或者實體Bean試圖創(chuàng)建另一個會話Bean,,可能導致的CreateException,,或者會話Bean在它的某個容器回調方法中獲得了一個FinderException等,都最好將其作為系統(tǒng)異常,。此外,若考慮會話Bean在處理下報稅單時,,用戶須具有一個簡歷,,若沒有,則會話Bean將觸發(fā)ObjectNotFoundException異常,,這時最好將其作為應用程序異常拋出,,以告知用戶其簡歷丟失。
例2:logon()方法的InvalidUserDataException應用程序異常處理,。其代碼如下:
public void logon(String user, String password) throws InvalidUserDataException
{?if (user == null || password ==null)
??throw new InvalidUserDataException();
?serviceImpl.logon(user, password);
}
以下是應用程序異常類InvalidUserDataException的定義,。
public class InvalidUserDataException extends Exception
{? public InvalidUserDataException()
?{super(“用戶名或密碼不能為空!”); }
}
4? 結論
在企業(yè)級的大型軟件開發(fā)中,,嚴謹強大的Java異常處理機制為軟件質量控制提供了有力的技術支持,,提高了軟件的可讀性、可維護性,、容錯性和開發(fā)效率,。充分有效的利用Java異常處理機制、采用合適的異常處理策略,,是提高EJB中異常處理的性能的有效途徑,。
參考文獻:
[1]? Bruce Eckel. Thinking in Java[M].Beijing: China Machine Press,2000.240-281.
[2]? 趙化冰,唐英,唐文彬,蘆東昕. Java異常處理[J]. 計算機應用, 2003,12:46-48.
[3]? 張聰品,趙琛,糜宏斌. 異常處理機制研究[J].計算機應用研究,2005,4:86-89.
[4]? [美]Chuck Cavaness Brian Keeton著,智慧東方工作室 譯. EJB 2.0企業(yè)級應用程序開發(fā)[M].北京:機械工業(yè)出版社,2002,3.294-310.
[5]? EJB異常處理的最佳做法. http://www.evget.com/view/article/viewArticle.asp?article=548
[6]? Log4J框架. http://jakarta.apache.org/log4j/docs/index.html
[7]? Java Logging API. http://java.sun.com/j2se/1.4/docs/api/java/util/logging/
package-summary.html