在大多數Java項目中,大部分代碼都是樣板代碼。異常處理就屬於此類代碼。即使商務邏輯只有3到4行代碼,用於異常處理的代碼也要佔10到20行。本文將討論如何讓異常處理保持簡單和直觀,使開發人員可以專心於開發商務邏輯,而不是把時間浪費在編寫異常處理的樣板代碼上。本文還將說明用於在J2EE環境中建立和處理異常的基礎知識和指導原則,並提出了一些可以使用異常解決的業務問題。本文將使用Struts架構作為表示實現,但該方法適用於任何錶示實現。
使用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
}
...