java web 項目異常管理

在實際的j2ee項目中,系統內部不免會出現一些異常,若是把異常聽任無論直接打印到瀏覽器可能會讓用戶感受莫名其妙,也有可能讓某些用戶找到破解系統的方法。 java

出來工做一年時間了,我也大概對異常處理有了一些瞭解,在這呢小弟簡單介紹下我的對異常處理的看法,拋磚引玉,但願各位大神提出寶貴的意見和建議。 程序員

 

就拿spring+struts2+hibernate項目說明:一般一個頁面請求到後臺之後,首先是到action(也就是所謂mvc的 controller),在action層會調用業務邏輯service,servce層會調用持久層dao獲取數據。最後執行結果會彙總到 action,而後經過action控制轉發到指定頁面,執行流程以下圖所示: ajax



 

而這三層其實都有可能發生異常,好比dao層可能會有SQLException,service可能會有 NullPointException,action可能會有IOException,一但發生異常而且程序員未作處理,那麼該層不會再往下執行,而是向 調用本身的方法拋出異常,若是dao、service、action層都未處理異常的話,異常信息會拋到服務器,而後服務器會把異常直接打印到頁面,結果 就會以下圖所示: spring



 

 

其實這種錯誤對於客戶來講毫無心義,由於他們一般是看不懂這是什麼意思的。 數據庫

剛學java的時候,咱們處理異常一般兩種方法:①直接throws,聽任無論;②寫try...catch,在catch塊中不做任何操做,或者 僅僅printStackTrace()把異常打印到控制檯。第一種方法最後就造就了上圖的結果;而第二種方法更杯具:頁面不報錯,可是也不執行用戶的請 求,簡單的說,其實這就是bug(委婉點:一般是這樣)! json

 

那麼發生異常到底應該怎麼辦呢?我想在你們對java異常有必定了解之後,會知道:異常應該在action控制轉發以前儘可能處理,同時記錄log日誌,而後在頁面以友好的錯誤提示告訴用戶出錯了。你們看下面的代碼: 瀏覽器

Java代碼   收藏代碼
  1. //建立日誌對象  
  2. Log log = LogFactory.getLog(this.getClass());  
  3.   
  4. //action層執行數據添加操做  
  5. public String save(){  
  6.    try{  
  7.          //調用service的save方法  
  8.          service.save(obj);  
  9.    }catch(Exception e){  
  10.          log.error(...);   //記錄log日誌  
  11.       return "error"; 到指定error頁面  
  12.    }  
  13.    return "success";  
  14. }  

 

若是按照上面的方式處理異常之後,咱們用戶最後看到的頁面可能就會是下面這種形式(我想這種錯誤提示應該稍微友好點了吧): 服務器



 

 

而後咱們回到剛纔處理異常的地方,若是你們積累了一些項目經驗之後會發現使用上面那種處理異常的方式可能還不夠靈活: mvc

①由於spring把大多數非運行時異常都轉換成運行時異常(RuntimeException)最後致使程序員根本不知道什麼地方應該進行try...catch操做 app

②每一個方法都重複寫try...catch,並且catch塊內的代碼都很類似,這明顯作了不少重複工做並且還很容易出錯,同時也加大了單元測試的用例數(項目經理一般喜歡根據代碼行來估算UT case)

③發生異常有不少種狀況:可能有數據庫增刪改查錯誤,多是文件讀寫錯誤,等等。用戶以爲每次發生異常都是「訪問過程當中產生錯誤,請重試」的提示完 全不能說明錯誤狀況,他們但願讓異常信息更詳盡些,好比:在執行數據刪除時發生錯誤,這樣他們能夠更準確地給維護人員提供bug信息。

 

如何解決上面的問題呢?我是這樣作的:JDK異常或自定義異常+異常攔截器

struts2攔截器的做用在網上有不少資料,在此再也不贅述,個人異常攔截器原理以下圖所示:



 首先個人action類、service類和dao類若是有必要捕獲異常,我都會try...catch,catch塊內不記錄log,一般是拋出一個新異常,而且註明錯誤信息:

Java代碼   收藏代碼
  1. //action層執行數據添加操做  
  2. public String save(){  
  3.    try{  
  4.          //調用service的save方法  
  5.          service.save(obj);  
  6.    }catch(Exception e){  
  7.       //你問我爲何拋出Runtime異常?由於我懶得在方法後寫throws  xx  
  8.       throw new RuntimeException("添加數據時發生錯誤!",e);  
  9.   }  
  10.    return "success";  
  11. }  

 

 

而後在異常攔截器對異常進行處理,看下面的代碼:

Java代碼   收藏代碼
  1. public String intercept(ActionInvocation actioninvocation) {  
  2.   
  3.         String result = null// Action的返回值  
  4.         try {  
  5.             // 運行被攔截的Action,期間若是發生異常會被catch住  
  6.             result = actioninvocation.invoke();  
  7.             return result;  
  8.         } catch (Exception e) {  
  9.             /** 
  10.              * 處理異常 
  11.              */  
  12.             String errorMsg = "未知錯誤!";  
  13.             //經過instanceof判斷究竟是什麼異常類型  
  14.             if (e instanceof BaseException) {  
  15.                 BaseException be = (BaseException) e;  
  16.                 be.printStackTrace(); //開發時打印異常信息,方便調試  
  17.                 if(be.getMessage()!=null||Constants.BLANK.equals(be.getMessage().trim())){  
  18.                     //得到錯誤信息  
  19.                     errorMsg = be.getMessage().trim();  
  20.                 }  
  21.             } else if(e instanceof RuntimeException){  
  22.                 //未知的運行時異常  
  23.                 RuntimeException re = (RuntimeException)e;  
  24.                 re.printStackTrace();  
  25.             } else{  
  26.                 //未知的嚴重異常  
  27.                 e.printStackTrace();  
  28.             }  
  29.             //把自定義錯誤信息  
  30.             HttpServletRequest request = (HttpServletRequest) actioninvocation  
  31.                     .getInvocationContext().get(StrutsStatics.HTTP_REQUEST);  
  32.               
  33.             /** 
  34.              * 發送錯誤消息到頁面 
  35.              */  
  36.             request.setAttribute("errorMsg", errorMsg);  
  37.           
  38.             /** 
  39.              * log4j記錄日誌 
  40.              */  
  41.             Log log = LogFactory  
  42.                     .getLog(actioninvocation.getAction().getClass());  
  43.             if (e.getCause() != null){  
  44.                 log.error(errorMsg, e);  
  45.             }else{  
  46.                 log.error(errorMsg, e);  
  47.             }  
  48.   
  49.             return "error";  
  50.         }// ...end of catch  
  51.     }  

 須要注意的是:在使用instanceof判斷異常類型的時候必定要從子到父依次找,好比BaseException繼承與RuntimeException,則必須首先判斷是不是BaseException再判斷是不是RuntimeException。

 

 

最後在error JSP頁面顯示具體的錯誤消息便可:

Java代碼   收藏代碼
  1. <body>  
  2. <s:if test="%{#request.errorMsg==null}">  
  3.     <p>對不起,系統發生了未知的錯誤</p>  
  4. </s:if>  
  5. <s:else>  
  6.     <p>${requestScope.errorMsg}</p>  
  7. </s:else>  
  8. </body>  

 

以上方式能夠攔截後臺代碼全部的異常,但若是出現數據庫鏈接異常時不能被捕獲的,你們可使用struts2的全局異常處理機制來處理:

Java代碼   收藏代碼
  1. <global-results>  
  2.     <result name="error" >/Web/common/page/error.jsp</result>  
  3. </global-results>  
  4.            
  5. <global-exception-mappings>  
  6.     <exception-mapping result="error" exception="java.lang.Exception"></exception-mapping>  
  7. </global-exception-mappings>  

 

上面這是一個很簡單的異常攔截器,你們可使用自定義異常,那樣會更靈活一些。

 

以上異常攔截器可使用其它不少技術替換:好比spring aop,servlet filter等,根據項目實際狀況處理。

 

【補充】ajax也能夠進行攔截,可是由於ajax屬於異步操做,action經過response形式直接把數據返回給ajax回調函數,若是發生異常,ajax是不會執行頁面跳轉的,因此必須把錯誤信息返回給回調函數,我針對json數據的ajax是這樣作的:

Java代碼   收藏代碼
  1. /** 
  2.  * 讀取文件,獲取對應錯誤消息 
  3.  */  
  4. HttpServletResponse response = (HttpServletResponse)actioninvocation.getInvocationContext().get(StrutsStatics.HTTP_RESPONSE);  
  5. response.setCharacterEncoding(Constants.ENCODING_UTF8);  
  6. /** 
  7.  * 發送錯誤消息到頁面 
  8.  */  
  9. PrintWriter out;  
  10. try {  
  11.     out = response.getWriter();  
  12.     Message msg = new Message(errorMsg);  
  13.     //把異常信息轉換成json格式返回給前臺  
  14.     out.print(JSONObject.fromObject(msg).toString());  
  15. catch (IOException e1) {  
  16.     throw e;  
  17. }  

 

 

 

以上是我的拙見,勿拍磚,謝謝。

相關文章
相關標籤/搜索