由於JavaWeb應用業務邏輯的復雜性,容易發生一些意想不到的錯誤和異常,給系統的調試帶來不必要的麻煩,不友好的提示信息使編程者對錯誤和異常無從下手。特別是當發生異常時,Java異常棧輸出的信息只能給程序員來看,是絕對不能展示給用戶的。而且一旦項目發布成最終版本運行時,也十分有必要跟蹤和記錄所發生的錯誤和異常的詳細信息,並返回一個特定的友好界面給用戶。 在傳統的JavaWeb應用中,應用程序通常是采用硬編碼的方式來避免將要發生的錯誤和異常,對應用程序中的異常經常使用try…catch語句進行處理,哪 里可能會拋出異常,就在哪里進行捕獲,這些語句似乎成了應對編譯器而不得已的手段。為了簡化,往往在catch代碼塊中什么也不寫,try…catch語句成了一種擺設。甚至干脆就用關鍵字throws聲明拋出異常,不對異常進行處理。這樣不但會使寫出來的程序雜亂無序,到處都是一些無用的try…catch語句,程序的可讀性、可修改性大大降低,而且還會增加程序員的工作量。
針對以上問題,考慮到JavaWeb應用開發、日后運行、升級和維護,在Java異常機制的基礎上構建了一個在JavaWeb環境中的錯誤處理和異常處理的框架模型。該模型對異常和錯誤進行統一管理,並在一個集中的位置統一處理,程序的可讀性、可維護性、可修改性、魯棒性等都得到了提高。本文使用Struts、spring、hibernate架構三層JavaWeb應用。Struts作為表示層,Spring作為業務邏輯層,Hibernate作為持久層。
2.1錯誤和異常處理原則
本文對錯誤的處理方式是采用拋出自定義類型的異常,這樣便於對異常和錯誤進行統一管理,提高JavaWeb應用程序的健壯性。JavaWeb應用開發中產生的異常都應該繼承Exception(屬於checkedexcpetion類型)。而且JavaWeb應用一般采用三層或多層架構,程序員沒有必要在各個層中對錯誤和異常進行處理,應用中的每一層在包裝並傳遞異常時要過濾掉Runtime-Exception,從責任這個角度看uncheckedexception是程序應該負擔的責任;checkedexception是具體應用負擔的責任。無論如何我們都不應該將uncheckedexception這樣的異常暴露給客戶的,因為他們沒有解決這個問題的責任,應該將這種異常封裝成checkedexception類型的異常,由具體的應用程序來負擔這個責任。
2.2錯誤處理策略
程序中可能會發生很多的錯誤,例如當執行刪除記錄、插入記錄、修改記錄和復雜的業務邏輯等錯誤,當出現了錯誤應該如何處理呢?
傳統的處理方法是采用編程的方式來提高應用程序的健壯性。當發生錯誤時,由程序來控制給用戶提示友好信息或者顯示一個錯誤提示界面。很顯然這種處理方式的實質就是增加程序的代碼量來彌補程序中的不足,治標沒有治本,不能從根本上解決問題。
本文采用的錯誤處理策略是當發生錯誤時,將錯誤和發生錯誤時轉向的頁面封裝成一個異常對象將其拋出,然后將異常集中到一個統一的位置進行處理。顯而易見,采用這種錯誤處理的方式的優點在於:當運行中的程序發生錯誤時就拋出一個詳細的異常對象,根據發生的異常信息來決定轉向到不同的頁。避免因采用編程而被忽略的一些錯誤(由於代碼量的增加而導致的錯誤)。
2.3異常處理策略
程序中可能會發生很多的異常,例如業務邏輯、未找到指定的文件、類型轉換失敗等異常時,Web應用程序應該將異常和發生異常時轉向的頁面封裝成一個新的異常對象將其拋出,然后將異常集中到一個統一的位置進行處理。顯然,采用這種異常處理的方式的優點在於Java中的異常棧信息沒有展現給用戶,而是將異常信息和友好的頁面展現給用戶。
使用異常對應用程序錯誤和異常進行統一管理的好處在於:由於Java的異常機制允許調用者可以不對異常進行處理,而用關鍵字throws拋出異常,這將會使異常向上一級傳遞,即當前環境沒有足夠的信息和能力來解決這個異常時,就可以把這個異常交到一個更高級的有能力處理的環境中,在這里將做出對異常的處理,形成一個異常的傳遞鏈。這樣可以將所有的異常通過這樣的傳遞鏈集中到一個統一的位置進行統一處理。
它能使錯誤代碼變得更有條理,與采用硬編碼的方式來處理錯誤方式相比,代碼量會明顯減少,並且通過這種錯誤報告機制,只需在一個地方處理錯誤,即在所謂的“異常處理程序”中處理,這種方式節省了代碼,而且把“描述做什么事”的代碼和“出了問題怎么辦”的代碼相分離。總之,與采用硬編碼的方式來處理錯誤的方法相比,異常機制使代碼的閱讀、編寫和調試工作更加井井有條。
2.4異常拋出策略和捕獲位置
在圖1所示的JavaWeb三層架構模型中,我們可以利用Java的多態機制,只捕獲自定義的基類異常(例如BasicException),它包含了所有應用程序異常的默認行為,但是具體業務邏輯拋出的異常可以是BasicEx-ception類的任何子類異常,使用多態來隱藏異常的具體的實現類。這意味着BasicException異常(僅僅是這個異常)可以放到拋出checked異常的每個方法的throws子句中,不能包含其他任何應用程序異常。當應用程序發生了某個具體的異常(BasicException的派生類)時,應用程序應該做出在哪一界面上顯示哪條錯誤消息的決策,即在什么位置捕獲異常。本文選擇的位置是控制器(Struts的Action),它恰恰有根據不同的異常來設置不同的錯誤信息和跳轉到不同的錯誤頁面的能力,而且Action的位置是最接近客戶端表示層的,所以這個位置是最恰當的。
這樣所有的異常會在一個集中的公共位置得到處理,使用模板方法(TemplateMethod)設計模式並結合Struts的DispatchAction編寫一個模板方法,並在該模板方法中捕獲BasicException異常,這將會捕獲到所有的子類異常。因此我們采用的策略是:持久層中的所有方法都拋出BasicException異常,不對其處理;業務邏輯層中的所有方法采用像持久層中的策略,同樣不對異常進行處理,即拋出BasicEx-ception異常。利用這種傳播異常的通用機制,將異常以一種普適的方式集中到距離客戶端最近的控制器中處理。這樣處理有很多優點:不需要在throws子句中放入大量的checked異常;throws子句中只需要有一個異常,不需要再對應用程序異常使用混亂的catch塊;如果需要處理它們,一個catch塊(用於BasicException)就足夠了,程序員也不需要親自進行異常處理(日志記錄以及獲取錯誤代碼),使編程者更加專注於業務邏輯的處理,而錯誤和異常的處理與記錄可以使用后文提到的Facade接口完成。
3.錯誤與異常處理模型實現
3.1錯誤與異常層次結構
本文僅以兩類錯誤和兩類異常為例展開討論,但本文構建的錯誤和異常處理框架模型是適合任意多的錯誤和異常種類,只要它們間接繼承BasicException類,例如數據沒有找到類型的錯誤,例如DataNot-FoundExceptionextendsBasicException。邏輯異常:LogicaExceptionextendsBasicException數據查找錯誤:DataExceptionextendsBasicExcep-tion權限異常:RightExceptionextendsBasicException登錄錯誤:LoginExceptionextendsBasicException在具體工程項目實現時,可以根據需要增加錯誤和異常的種類。
3.2應用與模型交互
本框架模型是在StrutsAction層負責決定對錯誤與異常采取什么操作。這種決策涉及到識別拋出異常的代碼。此外還需要知道在處理錯誤與異常之后應該把錯誤消息重定向到哪一界面。我們需要對基於異常類型獲得錯誤代碼這個過程進行抽象,同時還應該執行日志記錄。我們把這些工作抽象成一個接口,該接口基於外觀設計模式也叫總管模式(Facade模式)[10~11],該模式為子系統中的一組接口提供一個統一外部訪問入口。
外觀定義了一個更高級別的接口,使子系統變得更加易於訪問,是用於處理所有派生自BasicException的異常的整個異常處理系統的外觀。也就是說該接口是連接應用程序與框架模型的一個橋梁,為復雜的異常處理框架模型提供一個統一的操作門面。采用這種設計模式的優點在於:
它為應用邏輯屏蔽了異常框架模型子系統的復雜性,使得異常框架模型系統更加容易使用。實現了子系統與應用邏輯之間的松耦合關系,而子系統內部的功能組件往往是緊耦合的。方便在子系統中添加新功能,只需要在Facade里添加新的方法,然后調用擁有新功能的類或方法就可以了,原來實際執行任務的類不需改變。如圖2所示,給出了異常處理模型的時序圖。
從圖2可以看出采用這種Facade模式使得業務邏輯控制器只與異常處理門面打交道,簡化了業務邏輯關於異常處理的編程,使程序員更加專注於業務邏輯的編程。如圖3所示,給出了異常處理模型的時序圖。
從圖3可以看出異常處理系統通過異常處理門面,調用異常工具類將發生的所有可能的錯誤和異常統一封裝成ExDTO對象返回給控制器,同時也將異常的詳細信息記錄到日志文件中有助於日后的調試以及查找錯誤。
下面給出一個在StrutsAction方法中進行異常處理的例子。
- try{
- /*
- 處理應用程序的業務邏輯
- 調用業務邏輯層的業務邏輯方法,該業務方法聲明拋出Ba-
- sicException
- */}
- catch(BasicExceptione){
- //定義錯誤和異常處理者
- ExceptionHandlereh=newExceptionHandler();ExDTOexDto=eh.handleException(“context”,user,ex);ActionMessagesmessages=newActionMessages();messages.add(ActionMessages.GLOBALMESSAGE,newActionMessage(exDTO.getMessageCode()));
- saveMessages(request,messages);//轉到用戶有好界面
- returnmapping.findForward("errorAndExceptionOccur");}
3.3精簡Struts的Action的代碼
從上面編寫Action的方法可以看出,每個方法都必須書寫捕獲異常的模板代碼,應該避免這樣的編碼方式,解決方式是利用Struts的DispatchAction的工作機制(這里不對該類的工作方式進行介紹,可以查看幫助文檔),並結合模板方法模式(TemplateMethod)[10,11]重寫模板方法execute,並將不變的處理錯誤和異常模板的代碼寫在execute方法中,把具體的可變的業務邏輯控制方法留給子類來實現。下面給出一個自定義的
- StrutsDispatchAction中的execute方法的具體實現例子。
- try{
- ActionForwardfoward=
- dispatchMethod(mapping,form,request,response,name);returnfoward;}
- //處理用戶定義錯誤和異常catch(BaseAppExceptione){
- //定義錯誤和異常處理者
- ExceptionHandlereh=newExceptionHandler();ExDTOexDto=eh.handleException(expDTO.getContext(),userId,e);
- ActionMessagesmessages=newActionMessages();messages.add(ActionMessages.GLOBAL_MESSAGE,newActionMessage(exDTO.getMessageCode()));saveMessages(request,messages);//轉到用戶有好界面
- returnmapping.findForward("errorAndExceptionOccur");}
- //處理非用戶定義錯誤和異常catch(Exceptionex){
- //對錯誤和異常進行記錄
- ExceptionUtil.logException(this.getClass(),ex,userId);throwex;
- }finally
- {exDisplay.set(null);}
經過這樣的處理,每個Structs的Action只要繼承DispatchAction類就可以自動繼承錯誤和異常處理代碼,節省大量代碼的編寫。
4.結語
本文提出了一種關於錯誤處理和異常處理的框架模型,並利用三層架構思想實現了該模型,解決了JavaWeb應用中錯誤處理和異常處理普遍存在的問題。當應用發生錯誤和異常時,該模型能將錯誤和異常詳細信息記錄到日志文件中,同時控制器能夠根據該信息將頁面跳轉到指定的網頁上。
由於本文重點介紹錯誤與異常處理模型,故可以選用不同的工具加以實現。例如表示層可以選用JSF、Struts2;業務邏輯層可以選用Jdon;持久層可以選用其他的ORM框架,例如KylinORM、MyBatis等。