摘要:在企業(yè)級的大型軟件開發(fā)中,嚴謹強大的Java異常處理" title="異常處理">異常處理機制為軟件質量控制提供了有力的技術支持,提高了軟件的可讀性、可維護性、容錯性和開發(fā)效率。深入分析了Java異常處理的基本機制及語法結構、總結了異常處理的基本原則,進一步探索了EJB中異常處理的方法,并給出了其關鍵技術、優(yōu)化策略和應用實例。?
關鍵詞:Java;EJB;異常處理。?
中國分類號:TP311.52 ??文獻標識碼:A?
Java Exception Handling Technique and Application in EJB?
GUO Guang-jun?? YANG Si-qing?? DAI Jing-guo?? HE Wen-hua?
(Department of Computer Science of Hunan University of Humanities and Science and Technology, Loudi Hunan 417000,China )?
Abstract:In the large-scale enterprise software development, the precise, strong exception handling mechanism of the Java provided powerful technique support for the software quality control, as well as, the readability, maintainability, fault-tolerance performance and development efficiency of the software are improved. In this paper, first the basic exception handling mechanism and syntax structure of the Java are thoroughly analyzed, and the basic principle of the exception handling is advanced, then the exception handling mechanism of the EJB is probed into, the key technique,optimization strategy and example of the exception handling in EJB are provided last.?
Keywords:Java; EJB; Exception Handling?
1? 引言?
傳統的基于函數返回錯誤代碼的錯誤處理方法存在明顯的不足,如程序邏輯復雜,結構不清;可靠性差,維護不便;返回信息有限,不直觀需譯碼;返回代碼標準化困難,代碼復用率低;在面向對象的應用系統中,有些如構造方法等沒有返回值而無法報告程序錯誤。因此,在Java中新的錯誤檢測和報告方法—異常處理機制應運而生。?
異常是指中斷程序正常執(zhí)行流程的錯誤事件,如程序打開不存在的文件、裝載不存在的類、網絡連接中斷、被零除、訪問數組越界、系統資源耗盡等。在Java中,異常[1](Exception,例外)是特殊的運行錯誤對象,是異常類的一個對象,而每個異常類代表一種運行錯誤,在異常類中封裝了該運行錯誤的信息和處理錯誤的方法等內容。Java異常處理機制的基本思想是由發(fā)現而不能處理錯誤的方法引發(fā)一個異常對象,然后由該方法的直接或間接調用者捕獲并處理這個錯誤。其優(yōu)越性有:在catch中傳播與捕獲錯誤信息,實現了錯誤代碼與業(yè)務邏輯的分離,結構清晰;可對錯誤類型分組并標準化;方便了對錯誤的定位與維護;能有效防止由于異常而導致程序運行崩潰,可靠性高;強制程序員考慮程序的容錯性、健壯性和安全性。?
2? Java異常處理機制?
2.1? 異常類?
2.1.1? 系統定義的異常類
異常類用于處理異常,分為系統定義的異常類和用戶自定義的異常類[1]。在java.lang包中提供的Throwable類是異常類層次結構的頂層類,Error類和Exception類是從Throwable類直接派生的兩個知名子類" title="子類">子類。?
Exception類:它代表了Java語言中異常的基本屬性,除Java預定義的由Exception類派生的諸多異常類外,還支持用戶擴展Exception類來實現自定義異常類。其構造函數主要有public Exception()和public Exception(String s)等;它從Throwable類繼承了若干方法,常用的有:public String toString()方法,用來返回異常類信息;public void printStackTrace()方法,默認在當前標準輸出設備上輸出當前異常對象的堆棧使用軌跡。Exception類定義的是非致命性錯誤,允許用戶編寫代碼來處理這類錯誤,并繼續(xù)程序的執(zhí)行。通常觸發(fā)異常(Exception)的原因有打開的文件不存在;網絡連接中斷;操作數超過允許范圍;想要加載的類文件不存在;試圖通過空的引用型變量訪問對象;數組下標越界等。?
Error類:它定義的錯誤是致命性錯誤,如虛擬機錯誤、裝載錯誤、動態(tài)連接錯誤,一般會導致程序停止執(zhí)行,通常是由于Java系統或執(zhí)行環(huán)境發(fā)生錯誤(Error)而導致的。由于這類異常主要與硬件、運行時系統有關,而不是由用戶程序本身拋出的,因此用戶程序不對這類異常進行處理。?
需指出,除java.lang包中定義的異常處理之外,其他的Java包中也包括異常。實際上幾乎每個Java包都定義了相應的異常類。此外,運行時異常RuntimeException類及其派生子類是Java程序員不用處理的異常。Java創(chuàng)建者認為運行時異常不應由程序來處理,而且程序也很難真正的對付運行時異常。?
2.1.2? 用戶自定義異常類
用戶自定義異常類是指擴展Exception類或其他某個已經存在的系統異常類或其他用戶異常類而形成新的異常類??梢越o新的異常類定義新的屬性和方法,或重載父類的屬性和方法,并使這些屬性和方法能夠體現該類所對應的錯誤信息。要特別注意的是:第一? 一個方法被覆蓋時,覆蓋的方法必須扔出與被覆蓋方法相同的異?;蚱洚惓n惖淖宇悾坏诙? 若父類拋出多個異常,則覆蓋方法只能拋出父類所拋出的異常的一個子集,或者說不能拋出新的異常。?
2.2? 基本機制與語法結構?
2.2.1? 基本機制
Java異常處理機制采用中斷模式[2],即引發(fā)并拋出異常后,中止正在執(zhí)行的程序塊,控制流轉至異常處理器,待完成異常處理后,再返回調用點繼續(xù)執(zhí)行。異常處理的基本算法是:?
Step1:拋出異常,即創(chuàng)建一個異常對象并將它交給運行時系統的過程;?
Step2:捕獲異常,即找到異常處理程序的過程:運行時系統從發(fā)生錯誤的方法開始回溯,在方法調用" title="方法調用">方法調用堆棧里向后搜索,直到找到能處理當前發(fā)生的異常的處理程序的方法;?
Step3:處理異常,即通過方法調用來實現對異常的處理;?
Step4:終止異常處理。若運行時系統在方法調用棧中遍歷了所有的方法而未找到合適的異常處理程序,則顯示缺省錯誤并終止執(zhí)行運行時系統的異常處理。?
2.2.2? 語法結構
Java異常處理機制通過throws、throw、try、catch和finally 5個關鍵詞來實現,分為三個基本部分[3]。?
·throws:此關鍵字統一定制并明確標明了一個方法可能拋出的各種異常,這些異常是該方法定義的一部分。其實質是允許將異常處理遞歸交給調用它的上一級方法去處理,此時Java編譯器會強制此方法的調用者必須將其放在調用方法的try、catch塊中以拋出并捕獲處理這些異常。?
·throw:此語句用來拋出緊跟其后的一個異常對象給此方法的調用者,此異常對象可用new創(chuàng)建,或者是一個Throwable的實例句柄通過參數傳到catch中。因為用戶自定義的異常不能由系統自動拋出,所以必須借助于throw語句來拋出各種錯誤情況所對應的異常,且要求在程序中的合適位置定義好用戶自定義的異常類。?
·try-catch-finally:此語句是Java錯誤處理的基本結構,主要用來捕獲和處理一個或多個異常。通常由try、catch、finally三個塊組成。?。﹖ry塊:將所有可能拋出異常的代碼部分放入try塊中;ⅱ)catch塊:用緊跟在try塊后面一個或多個catch子句來捕獲異常,其的目標是處理異常,把變量設到合理的狀態(tài),并象沒有出錯一樣繼續(xù)運行。若一個子程序不處理這個異常,則可返回到上一級處理,如此不斷的遞歸向上直到最外一級。ⅲ)finally塊:finally是Java異常處理機制的精髓,使用finally可以維護對象的內部狀態(tài)、清理非內存資源、將系統恢復到應該處于的狀態(tài)。若沒有finally,要實現其功能的代碼是很費解的。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拋出“異?!保缓笤赾atch塊中捕獲之,實現程序中業(yè)務邏輯控制流程的跳轉。?
2.3? 異常處理的基本原則?
對于非運行時異常必須捕獲或聲明,而對運行時異常則不必,可以交給Java運行時系統來處理;對于自定義的異常類,通常把它作為Exception類的子類,且類名常以Exception結尾,不要把自定義的異常類作為RuntimeException類或Error的類的子類;在捕獲或聲明異常時,要選取合適類型的異常類,注意異常類的層次,根據不同的情況使用一般或特殊的異常類;根據具體的情況選擇在何處處理異常,或者在方法內捕獲并處理,或者用throws子句把它交給調用棧中上層的方法去處理;在catch語句中盡可能指定具體的異常類型,必要時使用多個catch;使用finally語句為異常處理提供統一的出口;若無法處理某個異常,則不捕獲它;若捕獲了一個異常,則要對它進行適當的處理;盡量在靠近異常被拋出的地方捕獲異常;除非要向上層遞歸拋出異常,否則要在捕獲異常的地方將其記錄到日志中。?
3? EJB中的異常處理?
3.1? EJB異常處理?
EJB(Enterprise JavaBean)是J2EE企業(yè)級應用開發(fā)的核心組件,EJB的分布式和事務屬性使得其異常處理成為一個更重要的問題[4]。EJB中異??煞譃槿?SUP>[5]:ⅰ)JVM異常:由JVM拋出,是一種致命錯誤。ⅱ)系統異常:一般由JVM以RuntimeException的子類拋出,是一種非受查異常。ⅲ)應用程序" title="應用程序">應用程序異常:它是一種定制異常,由應用程序或第三方類庫以Exception類或其子類拋出,是一種受查異常,通常由EJB方法的調用者來處理之。EJB容器攔截了EJB組件上的每一個方法調用,因此方法調用中發(fā)生的每一個異常也被EJB容器所攔截。EJB規(guī)范只處理應用程序異常和系統異常這兩種類型的異常。?
應用程序異常:是指在遠程接口的方法說明中所聲明的異常,它不是遠程異常RemoteException,也不應繼承RuntimeException或其子類。但是,在遠程接口方法的throws子句中聲明的非受查異常并不會被當作應用程序異常。應用程序異常是業(yè)務工作流中的例外,當這種類型的異常被拋出時,客戶機可得到一個恢復選項。當發(fā)生應用程序異常時,默認情況下EJB容器不會自動回滾事務,只有被顯式說明并通過調用關聯的EJBContext對象的setRollbackOnly()方法才能回滾事務。實際上,對于應用程序異常EJB容器通常以它原本的狀態(tài)傳送給客戶機而不會以任何方式包裝或修改它。?
系統異常:通常被定義為非受查異常,EJB方法不能從這種異常中恢復。當EJB容器攔截到非受查異常時,會自動回滾事務并執(zhí)行必要的清理工作,然后把該非受查異常包裝到RemoteException類或其子類中并拋給客戶機。對于受查異常,若要使用EJB容器的內務處理,則必須將受查異常作為非受查異常拋出。因此,每當觸發(fā)受查系統異常時,應該對其原始的異常以javax.ejb.EJBException類或其子類方式包裝后拋出。由于EJBException本身是非受查異常,因此不需要在方法的throws子句中聲明它。EJB容器會自動捕獲EJBException或其子類,并把它包裝到RemoteException中,然后拋給客戶機。?
需指出,雖然EJB規(guī)范規(guī)定系統異常由應用程序服務器記錄,但記錄格式會因應用程序服務器的不同而異。為確保異常記錄格式的統一,方便對其進行統計分析,在代碼中記錄異常是一種好的策略。此外,在EJB1.0規(guī)范中要求把受查系統異常以RemoteException拋出,而EJB 1.1及以上規(guī)范則規(guī)定EJB實現類絕不應拋出RemoteException。?
3.2? 關鍵技術?
3.2.1? 日志機制
盡管用System.out.println()方法跟蹤異常方便,但開銷大,對I/O處理的同步控制將大大降低系統吞吐量。缺省時堆棧跟蹤被輸出至控制臺,但在實際的應用系統中,通過瀏覽控制臺來查看異常跟蹤不太現實。因此,在大型應用系統的開發(fā)、測試和運行等生命周期中,采用日志機制和恰當的編碼策略,精確地記錄各種類型的異常,可以降低系統開銷,提高軟件性能和質量。知名的日志實用程序有兩種:Log4J[6]是Apache的Jakarta的一個開放源代碼的項目,J2SE 1.4捆綁提供了日志處理包(java.util.logging)[7],它們的使用方法請參考相關文獻。?
3.2.2? Decorators設計模式
在面向對象的程序設計中若用一個對象(the Decorators)包裝另外一個對象,被稱為Decorators設計模式" title="設計模式">設計模式?;贒ecorators設計模式,通過包裝原始的異常消息并在EJB組件中將其重新拋出,以方便對該異常的查詢和訪問。其次,由于Log4J只能記錄String消息,所以要利用Decorators設計模式定義一個專門類負責把堆棧跟蹤轉換成String,以獲取該堆棧跟蹤的String表示。?
3.3? EJB異常處理策略?
3.3.1? 常見EJB異常處理的不足
·拋出帶有出錯消息的異常:此種方法存在異常內容可能被“吞掉”的現象。?
·記錄到控制臺并拋出一個異常:僅當控制臺可用時調用者才能向后跟蹤。?
·包裝原始的異常以保護其內容:可能導致重復記錄,產品日志或控制臺不能被交叉引用。?
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。與這些類似的受查系統異常應該被捕獲,并被包裝起來,作為EJBException或它的一個子類拋出。在優(yōu)化的EJB設計中,客戶機一般不直接調用實體Bean上的方法,而通過會話Bean間接訪問實體Bean。若會話Bean調用相同的實體Bean方法,則要避免對異常的重復記錄。會話Bean和實體Bean處理系統異常的方式基本相似??刹捎迷L問標識技術避免對同一異常的重復記錄。?
·優(yōu)化系統異常處理:比應用程序異常處理更為復雜,它的發(fā)生不受EJB開發(fā)者的控制且異常信息不直觀,需要對其原始異常進行包裝,以清楚地表達系統異常的含義。?
·優(yōu)化Web層設計:通常把異常記錄到Web層本身,則易于實現且代碼簡潔。這要求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) { ?
//作為應用程序異常,還是系統異常??
} catch (RemoteException re) { ?
//這是系統異常,并記錄在日志中。?
throw ExceptionLogUtil.createLoggableEJBException(re); ?
} ?
return null; ?
} ?
例1中報稅單RatepayingOrder實體Bean的ejbCreate()方法試圖獲取納稅人Taxpayer實體Bean的一個遠程引用,可能導致FinderException。此處,盡管可把FinderException當作應用程序異?;蜃飨到y異常,但是若把它當系統異常則更好,因為這可以提高EJB組件對客戶機的透明性。同理,對于會話Bean或者實體Bean試圖創(chuàng)建另一個會話Bean,可能導致的CreateException,或者會話Bean在它的某個容器回調方法中獲得了一個FinderException等,都最好將其作為系統異常。此外,若考慮會話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?