一個用於J2EE應用程序的異常處理框架 – JAVA編程語言程序開發技術文章

使用checked和unchecked異常的場景

  您是否曾經想過,為什麼要在編寫好的代碼塊周圍放置一個try-catch塊,即便明知道無法對這些異常進行什麼處理,而隻滿足於把它們放在catch塊中?您可能想知道,為什麼不能把這項工作放在一個集中的地方完成?在大多數情況下,這個地方對於J2EE應用程序來說就是一個前端控制器。換句話說,開發人員不會因為它們而受到幹擾,因為根本不必很多地過問它們。但是,如果一個方法名稱包含一個throws子句,會出現什麼情況呢?開發人員或者必須捕捉這些異常,或者把它們放在自己的方法的throws子句中。這就是痛苦的根源!幸運的是,Java API有一類叫做unchecked exception的異常,它們不必捕捉。但是,仍然存在一個問題:根據什麼來決定哪些是checked異常,哪些是unchecked異常?下面給出一些指導原則:

  • 終端用戶無法采取有效操作的異常應該作為unchecked異常。例如,致命的和不可恢復的異常就應該是unchecked。把XMLParseException(在解析XML文件時拋出)作為checked異常沒有任何意義,因為惟一能夠采取的措施就是基於異常跟蹤來解決根本問題。通過擴展java.lang.RuntimeException,可以創建自定義的unchecked異常。
  • 在應用程序中,與用戶操作相關的異常應該是checked異常。checked異常要求客戶端來捕捉它們。您可能會問,為什麼不把所有異常都當作是unchecked。這樣做的問題在於,其中一些異常無法在正確的位置被捕捉到。這會帶來更大的問題,因為錯誤隻有在運行時才能被識別。checked異常的例子有業務確認異常、安全性異常等等。

異常拋出策略

隻捕捉基本應用程序異常(假定為BaseAppException)並在throws子句中聲明

  在大多數J2EE應用程序中,關於針對某個異常應該在哪一界面上顯示哪條錯誤消息的決策隻能在表示層中做出。這會帶來另一個問題:為什麼我們不能把這種決策放在一個公共的地方呢?在J2EE應用程序中,前端控制器就是一個進行常見處理的集中位置。

  此外,必須有一種用於傳播異常的通用機制。異常也需要以一種普適的方式得到處理。為此,我們始終需要在控制器端捕捉基本應用程序異常BaseAppException。這意味著我們需要把BaseAppException異常(隻有這個異常)放入可以拋出checked異常的每個方法的throws子句中。這裡的概念是使用多態來隱藏異常的實際實現。我們在控制器中捕捉BaseAppException,但是所拋出的特定異常實例可能是幾個派生異常類中的任意一個。借助於這種方法,可以獲得許多異常處理方面的靈活性:

  • 不需要在throws子句中放入大量的checked異常。throws子句中隻需要有一個異常。
  • 不需要再對應用程序異常使用混亂的catch塊。如果需要處理它們,一個catch塊(用於BaseAppException)就足夠瞭。
  • 開發人員不需要親自進行異常處理(日志記錄以及獲取錯誤代碼)。這種抽象是由ExceptionHandler完成的,稍後本文會就此點進行討論。
  • 即使稍後把更多異常引入到方法實現中,方法名稱也不會改變,因此也不需要修改客戶端代碼,否則就會引起連鎖反應。然而,拋出的異常需要在方法的Javadoc中指定,以便讓客戶端可以看到方法約束。

  下面給出拋出checked異常的一個例子:

public void updateUser(UserDTO userDTO)    throws BaseAppException{     UserDAO userDAO = new UserDAO();     UserDAO.updateUser(userDTO);     ...     if(...)         throw new RegionNotActiveException(             "Selected region is not active"); }  Controller Method: ... try{     User user = new User();     user.updateUser(userDTO); }catch(BaseAppException ex){     //ExceptionHandler is used to handle     //all exceptions derived from BaseAppException } ... 

  迄今為止,我們已經說明,對於所有可能拋出checked異常並被Controller調用的方法,其throws子句中應該隻包含checked異常。然而,這實際上暗示著我們在throws子句中不能包含其他任何應用程序異常。但是,如果需要基於catch塊中某種類型的異常來執行業務邏輯,那又該怎麼辦呢?要處理這類情況,方法還可以拋出一個特定異常。記住,這是一種特例,開發人員絕對不能認為這是理所當然的。同樣,此處討論的應用程序異常應該擴展BaseAppException類。下面給出一個例子:

CustomerDAO method: //throws CustomerNotActiveException along with //BaseAppException public CustomerDTO getCustomer(InputDTO inputDTO)     throws BaseAppException,         CustomerNotActiveException {     . . .     //Make a DB call to fetch the customer      //details based o­n inputDTO     . . .     // if not details found     throw new CustomerNotActiveException(         "Customer is not active"); }  Client method:  //catch CustomerNotActiveException //and continues its processing public CustomerDTO getCustomerDetails(   UserDTO userDTO)     throws BaseAppException{     ...     CustomerDTO custDTO = null;     try{         //Get customer details          //from local database         customerDAO.getCustomerFromLocalDB();     }catch(CustomerNotActiveException){         ...         return customerDAO             .activateCustomerDetails();     } } 

在web應用程序層次上處理unchecked異常

  所有unchecked異常都應該在web應用程序層次上進行處理。可以在web.xml文件中配置web頁面,以便當應用程序中出現unchecked異常時,可以把這個web頁面顯示給終端用戶。

把第三方異常包裝到特定於應用程序的異常中

  當一個異常起源於另一個外部接口(組件)時,應該把它包裝到一個特定於應用程序的異常中,並進行相應處理。

  例子:

try {     BeanUtils.copyProperties(areaDTO, areaForm); } catch (Exception se) {     throw new CopyPropertiesException(         "Exception occurred while using              copy properties", se); } 

  這裡,CopyPropertiesException擴展瞭java.lang.RuntimeException,我們將會記錄它。我們捕捉的是Exception,而不是copyProperties方法可以拋出的特定checked異常,因為對於所有這些異常來說,我們都會拋出同一個unchecked CopyPropertiesException異常。

過多異常

  您可能想知道,如果我們為每條錯誤消息創建一個異常,異常類自身是否會溢出呢?例如,如果“Order not found”是OrderNotFoundException的一條錯誤消息,您肯定不會讓CustomerNotFoundException的錯誤消息為“Customer not found”,理由很明顯:這兩個異常代表同樣的意義,惟一的區別在於使用它們的上下文不同。所以,如果可以在處理異常時指定上下文,我們無疑可以把這些異常合並為一個RecordNotFoundException。下面給出一個例子:

try{     ... }catch(BaseAppException ex){     IExceptionHandler eh =ExceptionHandlerFactory       .getInstance().create();     ExceptionDTO exDto = eh.handleException(         "employee.addEmployee", userId, ex); } 

  在這裡,employee.addEmployee上下文將被附加給一個上下文敏感的異常的錯誤代碼,從而產生惟一的錯誤代碼。例如,如果RecordNotFoundException的錯誤代碼是errorcode.recordnotfound,那麼這個上下文的最終錯誤代碼將變為errorcode.recordnotfound.employee.addEmployee,它對於這個上下文是惟一的錯誤代碼。

  然而,我們要給出一個警告:如果您準備在同一個客戶端方法中使用多個接口,而且這些接口都可以拋出RecordNotFoundException異常,那麼想要知道是哪個實體引發瞭這個異常就變得十分困難。如果業務接口是公共的,而且可以被各種外部客戶端使用,那麼建議隻使用特定的異常,而不使用像RecordNotFoundException這樣的一般性異常。特定於上下文的異常對於基於數據庫的可恢復異常來說非常有用,因為在這種情況下,異常類始終是相同的,不同的隻有它們出現的上下文。

J2EE應用程序的異常層次結構

  正如前面討論的那樣,我們需要定義一個異常基類,叫做BaseAppException,它包含瞭所有應用程序異常的默認行為。我們將把這個基類放到所有可能拋出checked異常的方法的throws子句中。應用程序的所有checked異常都應該是這個基類的子類。有多種定義錯誤處理抽象的方式。然而,其中的區別更多地是與業務類而不是與技術有關。對錯誤處理的抽象可分為以下幾類。所有這些異常類都是從BaseAppException派生而來。

checked異常

  • 業務異常:執行業務邏輯時出現的異常。BaseBusinessException是這類異常的基類。
  • 數據庫異常:與持久化機制進行交互時拋出的異常。BaseDBException是這類異常的基類。
  • 安全性異常:執行安全性操作時出現的異常。這類異常的基類是BaseSecurityException。
  • 確認異常:在從終端用戶處獲得確認以執行某個特定任務時使用。這類異常的基類是BaseConfirmationException。

unchecked異常

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *