前言
程序運行時出現異常是很常見的,有時候在本地測試都沒問題,一旦部署到生產服務器運行一段時間就出現問題。這個時候,我們就應該在代碼中對可能產生異常的地方(常見的對數據庫的操作)編寫異常處理的代碼,或是響應給客戶端,或是將異常記錄到日誌(比如log4j),然後在進行處理。本文主要講的是個人對 try catch 和throws對異常的處理認識。
對try catch的認識
這個常用的是try{code}catch(){}來對try裏面的代碼進行檢查,如果運行時出現異常,就會進入進入catch 裏面的代碼,那麼有個小問題,當catch 完之後,程序是否有返回值(如果這個方法有返回值),項目是否會繼續運行??
我們先來看下面的測試代碼:
public class TestTryCatch {
public static int myTest(){
int i = 0;
try {
int x = 5;
int y = 0;
int z = x/y;
} catch (Exception e) {
i= 1;
e.printStackTrace();
}
return i;
}
public static void main(String[] args) {
int i = myTest();
System.out.println("i="+i);
}
}
對於這個簡單的例子,在不放進編輯器運行的時候,你覺得i的值會是什麼??下面有三種選擇:A、0 B、1 C、程序停止,沒有返回值。
最終運行結果是:B 1 。
這個簡單的例子,其實跟我之前的認識是有區別的。我一直認爲當程序遇到異常,也就是try catch 之後,程序不會有返回值,程序在catch 之後會停止,但是很明顯我的認識是錯誤的。其實這裏在編碼的時候,如果是我們以我的認識,那麼將會導致一個bug,請看下面的例子:比如說有個送流量的活動,每次輸入號碼的時候要先查詢是否領取,如果領取了將不能再領取。
public static String checkUserTraffic(String number,String returnCode,String nowTime) {
String flow = null;
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "select flow from FLOW_INFO where PHONE = ? and RETURNCODE = ? and RECIEVETIME = ?";
conn = GreenetHomeConnecFactory.getConnection();
ps = conn.prepareStatement(sql);
ps.setString(1, number);
ps.setString(2, returnCode);
ps.setString(3, nowTime);
rs = ps.executeQuery();
while (rs.next()) {
flow = rs.getString("FLOW");
System.out.println("flow:" + flow);
}
} catch (SQLException e) {
Log.error("查詢是否領取流量失敗,參數"+"number="+number+",returnCode="+returnCode+",nowTime="+nowTime+"異常信息:"+e.toString());
} finally {
// conn.setAutoCommit(true);
GreenetHomeConnecFactory.close(conn, ps, rs);
}
return flow;
}
大家有沒有看出代碼中隱藏的bug?沒錯,就是沒對異常做出正確的處理,這裏對異常處理只打印了輸入參數。但是你發現沒有,如果程序遇到異常,或者數據庫不存在那個用戶的記錄,flow都會返回null;比如說當用戶輸入已經領取過流量的號碼時,理論上是不能在領取的,但是這時候程序出現異常,返回的是null,那麼他將會成功的再領一次流量,天大的漏洞啊。所以在這裏有個比較的做法是,在catch這裏給flow賦值,比如說給個error,然後控制器可以知道遇到了異常,直接將結果返回,不需要再繼續下去,這樣可以避免損失。
404找不到頁面處理
這個其實比較簡單,做個用於展示404的頁面,然後在web.xml配置即可。
<!-- 設置錯誤頁面 -->
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/views/error/404.jsp</location>
</error-page>
全局異常處理器
其實這個纔是我想說的重點。關於全局異常處理器,很多博客都會有介紹,我要說的是完善全局異常的輸出信息。
首先全局異常器是在springmvc中配置的
<bean id="resolveExceptionclass="com.aotain.read.controller.ExceptionHandlerController"/>
然後當遇到異常的時候,正常來說,如果沒有try catch處理,你可以寫throws 或者不寫,他都會進入異常處理器。下面是處理器的代碼:
public class ExceptionHandlerController implements HandlerExceptionResolver{
private static Logger Log = Logger.getLogger("sysLog");
public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) {
//轉發到異常頁面並輸出打印日誌(對於異步請求不會轉發頁面)
ModelAndView mav = new ModelAndView("error/500");
Log.error("異常信息:"+ex);
return mav;
}
}
不知道大家有沒有發現個問題,這裏對異常的處理不是很好,返回一個視圖告知用戶,還行;但是沒把具體出異常的堆棧信息(具體是什麼異常,在那些代碼中出現這個異常)打印出來,這對於我們發現和解決問題造成一定的困難,因爲出現異常的時候,單憑簡單的異常類型就去主觀臆斷,不是很好。
所以我就去百度了下Exception 都有哪些方法。
一開始我發現的就是這三個:
返回類型 方法 結果
String getMessage() 異常的類型
String ex.toString() 異常的類型
printStackTrace() 完整的堆棧信息
第三個方法的結果就是我們需要的,但是返回類型爲void,但是其它兩個方法又不夠具體。後來我看到了這個博客Java 打印堆棧的幾種方法,我從這裏找到了答案,修改了寫法
/**
* 全局異常捕捉器
* @author huangjg
*2017年10月27日
*
*/
public class ExceptionHandlerController implements HandlerExceptionResolver{
private static Logger Log = Logger.getLogger("sysLog");
public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) {
ModelAndView mav = new ModelAndView("error/500");//轉發到異常頁面並輸出打印日誌(對於異步請求不會轉發頁面)
StackTraceElement[] sTraceElements = ex.getStackTrace();
Log.error("異常信息:"+ex.getMessage());
for (int i = 0; i < sTraceElements.length; i++) {
Log.error("異常位置:"+sTraceElements[i].getClassName()+"."+sTraceElements[i].getFileName()+"."+sTraceElements[i].getMethodName()+":"+sTraceElements[i].getLineNumber());
}
return mav;
}
}
通過遍歷sTraceElements和log4j的結合,一方面可以記錄出現異常的堆棧信息,一方面也可以也可以給客戶端一個響應,並且我做測試的時候,不會像try catch一樣會有返回值(如果有返回值),而是直接進入了異常機制,這樣程序不會照着正常的流程走下去。不過這裏要注意的是,如果異常被你try catch到的,就會進入catch 處理,就算你配置了全局異常處理器,也不會進入異常處理器那裏。但是我也發現個小問題,如果是客戶端發情ajax異步請求,也就是說出現異常的時候不會發生跳轉,這樣就無法通知客戶端唉,這裏還沒找到合理的解決方法。
結語
以上是我對異常管理簡單的認識,如果有朋友發現我寫的不好或者有什麼好的意見的都可以給我留言,大家交流交流。