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