Java Web項(xiàng)目整體異常處理機(jī)制
在實(shí)際的j2ee項(xiàng)目中,系統(tǒng)內(nèi)部難免會(huì)出現(xiàn)一些異常,如果把異常放任不管直接打印到瀏覽器可能會(huì)讓用戶感覺莫名其妙,也有可能讓某些用戶找到破解系統(tǒng)的方法。
出來工作一年時(shí)間了,我也大概對(duì)異常處理有了一些了解,在這呢小弟簡(jiǎn)單介紹下個(gè)人對(duì)異常處理的見解,拋磚引玉,希望各位大神提出寶貴的意見和建議。
就拿spring+struts2+hibernate項(xiàng)目說明:通常一個(gè)頁面請(qǐng)求到后臺(tái)以后,首先是到action(也就是所謂mvc的 controller),在action層會(huì)調(diào)用業(yè)務(wù)邏輯service,servce層會(huì)調(diào)用持久層dao獲取數(shù)據(jù)。最后執(zhí)行結(jié)果會(huì)匯總到 action,然后通過action控制轉(zhuǎn)發(fā)到指定頁面,執(zhí)行流程如下圖所示:
而這三層其實(shí)都有可能發(fā)生異常,比如dao層可能會(huì)有SQLException,service可能會(huì)有 NullPointException,action可能會(huì)有IOException,一但發(fā)生異常并且程序員未做處理,那么該層不會(huì)再往下執(zhí)行,而是向 調(diào)用自己的方法拋出異常,如果dao、service、action層都未處理異常的話,異常信息會(huì)拋到服務(wù)器,然后服務(wù)器會(huì)把異常直接打印到頁面,結(jié)果 就會(huì)如下圖所示:
其實(shí)這種錯(cuò)誤對(duì)于客戶來說毫無意義,因?yàn)樗麄兺ǔJ强床欢@是什么意思的。
剛學(xué)java的 時(shí)候,我們處理異常通常兩種方法:①直接throws,放任不管;②寫try...catch,在catch塊中不作任何操作,或者僅僅 printStackTrace()把異常打印到控制臺(tái)。第一種方法最后就造就了上圖的結(jié)果;而第二種方法更杯具:頁面不報(bào)錯(cuò),但是也不執(zhí)行用戶的請(qǐng)求, 簡(jiǎn)單的說,其實(shí)這就是bug(委婉點(diǎn):通常是這樣)!
那么發(fā)生異常到底應(yīng)該怎么辦呢?我想在大家對(duì)java異常有一定了解以后,會(huì)知道:異常應(yīng)該在action控制轉(zhuǎn)發(fā)之前盡量處理,同時(shí)記錄log日志,然后在頁面以友好的錯(cuò)誤提示告訴用戶出錯(cuò)了。大家看下面的代碼:
//創(chuàng)建日志對(duì)象 Log log = LogFactory.getLog(this.getClass()); //action層執(zhí)行數(shù)據(jù)添加操作 public String save(){ try{ //調(diào)用service的save方法 service.save(obj); }catch(Exception e){ log.error(...); //記錄log日志 return "error"; 到指定error頁面 } return "success"; } |
如果按照上面的方式處理異常以后,我們用戶最后看到的頁面可能就會(huì)是下面這種形式(我想這種錯(cuò)誤提示應(yīng)該稍微友好點(diǎn)了吧):
然后我們回到剛才處理異常的地方,如果大家積累了一些項(xiàng)目經(jīng)驗(yàn)以后會(huì)發(fā)現(xiàn)使用上面那種處理異常的方式可能還不夠靈活:
①因?yàn)閟pring把大多數(shù)非運(yùn)行時(shí)異常都轉(zhuǎn)換成運(yùn)行時(shí)異常(RuntimeException)最后導(dǎo)致程序員根本不知道什么地方應(yīng)該進(jìn)行try...catch操作
②每個(gè)方法都重復(fù)寫try...catch,而且catch塊內(nèi)的代碼都很相似,這明顯做了很多重復(fù)工作而且還很容易出錯(cuò),同時(shí)也加大了單元測(cè)試的用例數(shù)(項(xiàng)目經(jīng)理通常喜歡根據(jù)代碼行來估算UT case)
③發(fā)生異常有很多種情況:可能有數(shù)據(jù)庫增刪改查錯(cuò)誤,可能是文件讀寫錯(cuò)誤,等等。用戶覺得每次發(fā)生異常都是“訪問過程中產(chǎn)生錯(cuò)誤,請(qǐng)重試”的提示完全不能說明錯(cuò)誤情況,他們希望讓異常信息更詳盡些,比如:在執(zhí)行數(shù)據(jù)刪除時(shí)發(fā)生錯(cuò)誤,這樣他們可以更準(zhǔn)確地給維護(hù)人員提供bug信息。
如何解決上面的問題呢?我是這樣做的:JDK異常或自定義異常+異常攔截器
struts2攔截器的作用在網(wǎng)上有很多資料,在此不再贅述,我的異常攔截器原理如下圖所示:
首先我的action類、service類和dao類如果有必要捕獲異常,我都會(huì)try...catch,catch塊內(nèi)不記錄log,通常是拋出一個(gè)新異常,并且注明錯(cuò)誤信息:
//action層執(zhí)行數(shù)據(jù)添加操作 public String save(){ try{ //調(diào)用service的save方法 service.save(obj); }catch(Exception e){ //你問我為什么拋出Runtime異常?因?yàn)槲覒械迷诜椒ê髮憈hrows xx throw new RuntimeException("添加數(shù)據(jù)時(shí)發(fā)生錯(cuò)誤!",e); } return "success"; } |
然后在異常攔截器對(duì)異常進(jìn)行處理,看下面的代碼:
public String intercept(ActionInvocation actioninvocation) {
String result = null; // Action的返回值
try {
// 運(yùn)行被攔截的Action,期間如果發(fā)生異常會(huì)被catch住
result = actioninvocation.invoke();
return result;
} catch (Exception e) {
/**
* 處理異常
*/
String errorMsg = "未知錯(cuò)誤!";
//通過instanceof判斷到底是什么異常類型
if (e instanceof BaseException) {
BaseException be = (BaseException) e;
be.printStackTrace(); //開發(fā)時(shí)打印異常信息,方便調(diào)試
if(be.getMessage()!=null||Constants.BLANK.equals(be.getMessage().trim())){
//獲得錯(cuò)誤信息
errorMsg = be.getMessage().trim();
}
} else if(e instanceof RuntimeException){
//未知的運(yùn)行時(shí)異常
RuntimeException re = (RuntimeException)e;
re.printStackTrace();
} else{
//未知的嚴(yán)重異常
e.printStackTrace();
}
//把自定義錯(cuò)誤信息
HttpServletRequest request = (HttpServletRequest) actioninvocation
.getInvocationContext().get(StrutsStatics.HTTP_REQUEST);
/**
* 發(fā)送錯(cuò)誤消息到頁面
*/
request.setAttribute("errorMsg", errorMsg);
/**
* log4j記錄日志
*/
Log log = LogFactory
.getLog(actioninvocation.getAction().getClass());
if (e.getCause() != null){
log.error(errorMsg, e);
}else{
log.error(errorMsg, e);
}
return "error";
}// ...end of catch
}
需要注意的是:在使用instanceof判斷異常類型的時(shí)候一定要從子到父依次找,比如BaseException繼承與RuntimeException,則必須首先判斷是否是BaseException再判斷是否是RuntimeException。
最后在error JSP頁面顯示具體的錯(cuò)誤消息即可:
<body> <s:if test="%{#request.errorMsg==null}"> <p>對(duì)不起,系統(tǒng)發(fā)生了未知的錯(cuò)誤</p> </s:if> <s:else> <p>${requestScope.errorMsg}</p> </s:else> </body> |
以上方式可以攔截后臺(tái)代碼所有的異常,但如果出現(xiàn)數(shù)據(jù)庫連接異常時(shí)不能被捕獲的,大家可以使用struts2的全局異常處理機(jī)制來處理:
<global-results> <result name="error" >/Web/common/page/error.jsp</result> </global-results> <global-exception-mappings> <exception-mapping result="error" exception="java.lang.Exception"></exception-mapping> </global-exception-mappings> |
上面這是一個(gè)很簡(jiǎn)單的異常攔截器,大家可以使用自定義異常,那樣會(huì)更靈活一些。
以上異常攔截器可以使用其它很多技術(shù)替換:比如spring aop,servlet filter等,根據(jù)項(xiàng)目實(shí)際情況處理。
【補(bǔ)充】ajax也可以進(jìn)行攔截,但是因?yàn)閍jax屬于異步操作,action通過response形式直接把數(shù)據(jù)返回給ajax回調(diào)函數(shù),如果發(fā)生異常,ajax是不會(huì)執(zhí)行頁面跳轉(zhuǎn)的,所以必須把錯(cuò)誤信息返回給回調(diào)函數(shù),我針對(duì)json數(shù)據(jù)的ajax是這樣做的:
/** * 讀取文件,獲取對(duì)應(yīng)錯(cuò)誤消息 */ HttpServletResponse response = (HttpServletResponse)actioninvocation.getInvocationContext().get(StrutsStatics.HTTP_RESPONSE); response.setCharacterEncoding(Constants.ENCODING_UTF8); /** * 發(fā)送錯(cuò)誤消息到頁面 */ PrintWriter out; try { out = response.getWriter(); Message msg = new Message(errorMsg); //把異常信息轉(zhuǎn)換成json格式返回給前臺(tái) out.print(JSONObject.fromObject(msg).toString()); } catch (IOException e1) { throw e; } |
以上是個(gè)人拙見,勿拍磚,謝謝。
posted on 2012-09-17 10:51 順其自然EVO 閱讀(1319) 評(píng)論(0) 編輯 收藏