java异常处理学习总结
生活随笔
收集整理的這篇文章主要介紹了
java异常处理学习总结
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
Java異常的棧軌跡(Stack Trace)
? ? 捕獲到異常時,往往需要進行一些處理。比較簡單直接的方式就是打印異常棧軌跡Stack Trace。說起棧軌跡,可能很多人和我一樣,第一反應就是printStackTrace()方法。其實除了這個方法,還有一些
別的內容也是和棧軌跡有關的。
? ? ?1.printStackTrace()
? ? 首先需要明確,這個方法并不是來自于Exception類。Exception類本身除了定義了幾個構造器之外
,所有的方法都是從其父類繼承過來的。而和異常相關的方法都是從java.lang.Throwable類繼承過來的
。而printStackTrace()就是其中一個。
? ? ?這個方法會將Throwable對象的棧軌跡信息打印到標準錯誤輸出流上。輸出的大體樣子如下:
java.lang.NullPointerException
? ? ? ? ?at MyClass.mash(MyClass.java:9)
? ? ? ? ?at MyClass.crunch(MyClass.java:6)
? ? ? ? ?at MyClass.main(MyClass.java:3)
? ?輸出的第一行是toString()方法的輸出,后面幾行的內容都是之前通過fillInStackTrace()方法保存
的內容。關于這個方法,我們后面會講。
? ? 下面看一個例子:
public class TestPrintStackTrace {
? ? public static void f() throws Exception{
? ? ? ? throw new Exception("出問題啦!");
? ? }
? ? public static void g() throws Exception{
? ? ? ? f();
? ? }
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? g();
? ? ? ? }catch(Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
? ? 這個例子的輸出如下:
java.lang.Exception: 出問題啦!
? ? at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
? ? at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
? ? at TestPrintStackTrace.main(TestPrintStackTrace.java:10)
? ? 在這個例子中,在方法f()中拋出異常,方法g()中調用方法f(),在main方法中捕獲異常,并且打印
棧軌跡信息。因此,輸出依次展示了f—>g—>main的過程。
? ? 2.getStackTrace()方法
? ? 這個方法提供了對printStackTrace()方法所打印信息的編程訪問。它會返回一個棧軌跡元素的數組
。以上面的輸出為例,輸出的第2-4行每一行的內容對應一個棧軌跡元素。將這些棧軌跡元素保存在一個
數組中。每個元素對應棧的一個棧幀。數組的第一個元素保存的是棧頂元素,也就是上面的f。最后一個
元素保存的棧底元素。
? ? 下面是一個使用getStackTrace()訪問這些軌跡棧元素并打印輸出的例子:
public class TestPrintStackTrace {
? ? public static void f() throws Exception{
? ? ? ? throw new Exception("出問題啦!");
? ? }
? ? public static void g() throws Exception{
? ? ? ? f();
? ? }
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? g();
? ? ? ? }catch(Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? System.out.println("------------------------------");
? ? ? ? ? ? for(StackTraceElement elem : e.getStackTrace()) {
? ? ? ? ? ? ? ? System.out.println(elem);
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
? ? 這樣的輸出和printStackTrace()的輸出基本上是一樣的,如下:
java.lang.Exception: 出問題啦!
? ? at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
? ? at TestPrintStackTrace.g(TestPrintStackTrace.java:6)
? ? at TestPrintStackTrace.main(TestPrintStackTrace.java:10)
TestPrintStackTrace.f(TestPrintStackTrace.java:3)
TestPrintStackTrace.g(TestPrintStackTrace.java:6)
TestPrintStackTrace.main(TestPrintStackTrace.java:10)
? ?3.fillInStackTrace()
? ? 我們在前面也提到了這個方法。要說清楚這個方法,首先要講一下捕獲異常之后重新拋出的問題。
在catch代碼塊中捕獲到異常,打印棧軌跡,又重新throw出去。在上一級的方法調用中,再捕獲這個異
常并且打印出棧軌跡信息。這兩個棧軌跡信息會一樣嗎?我們看一下代碼:
public class TestPrintStackTrace {
? ? public static void f() throws Exception{
? ? ? ? throw new Exception("出問題啦!");
? ? }
? ? public static void g() throws Exception{
? ? ? ? try {
? ? ? ? ? ? f();
? ? ? ? }catch(Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? throw e;
? ? ? ? }
? ? ? ? ?
? ? }
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? g();
? ? ? ? }catch(Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
? ? 在main方法中捕獲的異常,是在g()方法中拋出的,按理說這兩個打印棧軌跡的信息應該不同,第二
次打印的信息應該沒有關于f的信息。但是事實上,兩次打印棧軌跡信息是一樣的。輸出結果如下:
java.lang.Exception: 出問題啦!
? ? at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
? ? at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
? ? at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
java.lang.Exception: 出問題啦!
? ? at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
? ? at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
? ? at TestPrintStackTrace.main(TestPrintStackTrace.java:16)
? ? 也就是說,捕獲到異常又立即拋出,在上級方法調用中再次捕獲這個異常,打印的棧軌跡信息是一
樣的。原因在于沒有將當前線程當前狀態下的軌跡棧的狀態保存進Throwabe中?,F在我們引入
fillInStackTrace()方法。這個方法剛好做的就是這樣的保存工作。我們看一下這個方法的原型:
1
public Throwable fillInStackTrace()
? ? 這個方法是有返回值的。返回的是保存了當前棧軌跡信息的Throwable對象。我們看看使用
fillInStackTrace()方法處理后,打印的棧軌跡信息有什么不同,代碼如下:
public class TestPrintStackTrace {
? ? public static void f() throws Exception{
? ? ? ? throw new Exception("出問題啦!");
? ? }
? ? public static void g() throws Exception{
? ? ? ? try {
? ? ? ? ? ? f();
? ? ? ? }catch(Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? //不要忘了強制類型轉換
? ? ? ? ? ? throw (Exception)e.fillInStackTrace();
? ? ? ? }
? ? ? ? ?
? ? }
? ? public static void main(String[] args) {
? ? ? ? try {
? ? ? ? ? ? g();
? ? ? ? }catch(Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? }
? ? }
}
? ? ?輸出如下:
java.lang.Exception: 出問題啦!
? ? at TestPrintStackTrace.f(TestPrintStackTrace.java:3)
? ? at TestPrintStackTrace.g(TestPrintStackTrace.java:7)
? ? at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
java.lang.Exception: 出問題啦!
? ? at TestPrintStackTrace.g(TestPrintStackTrace.java:11)
? ? at TestPrintStackTrace.main(TestPrintStackTrace.java:17)
? ? 我們看到,在main方法中打印棧軌跡已經沒有了f相關的信息了。
? ? 以上就是關于Java棧軌跡的一些我之前沒有掌握的內容,記下來備忘。?
========
java web項目整體異常處理機制
在實際的j2ee項目中,系統內部難免會出現一些異常,如果把異常放任不管直接打印到瀏覽器可能會讓
用戶感覺莫名其妙,也有可能讓某些用戶找到破解系統的方法。
出來工作一年時間了,我也大概對異常處理有了一些了解,在這呢小弟簡單介紹下個人對異常處理的見
解,拋磚引玉,希望各位大神提出寶貴的意見和建議。
?
就拿spring+struts2+hibernate項目說明:通常一個頁面請求到后臺以后,首先是到action(也就是所
謂mvc的controller),在action層會調用業務邏輯service,servce層會調用持久層dao獲取數據。最后
執行結果會匯總到action,然后通過action控制轉發到指定頁面,執行流程如下圖所示:
?
而這三層其實都有可能發生異常,比如dao層可能會有SQLException,service可能會有
NullPointException,action可能會有IOException,一但發生異常并且程序員未做處理,那么該層不會
再往下執行,而是向調用自己的方法拋出異常,如果dao、service、action層都未處理異常的話,異常
信息會拋到服務器,然后服務器會把異常直接打印到頁面,結果就會如下圖所示:
?
其實這種錯誤對于客戶來說毫無意義,因為他們通常是看不懂這是什么意思的。
剛學java的時候,我們處理異常通常兩種方法:①直接throws,放任不管;②寫try...catch,在catch
塊中不作任何操作,或者僅僅printStackTrace()把異常打印到控制臺。第一種方法最后就造就了上圖的
結果;而第二種方法更杯具:頁面不報錯,但是也不執行用戶的請求,簡單的說,其實這就是bug(委婉
點:通常是這樣)!
?
那么發生異常到底應該怎么辦呢?我想在大家對java異常有一定了解以后,會知道:異常應該在action
控制轉發之前盡量處理,同時記錄log日志,然后在頁面以友好的錯誤提示告訴用戶出錯了。大家看下面
的代碼:
Java代碼 ?收藏代碼
//創建日志對象 ?
Log log = LogFactory.getLog(this.getClass()); ?
??
//action層執行數據添加操作 ?
public String save(){ ?
? ?try{ ?
? ? ? ? ?//調用service的save方法 ?
? ? ? ? ?service.save(obj); ?
? ?}catch(Exception e){ ?
? ? ? ? ?log.error(...); ? //記錄log日志 ?
? ? ? return "error"; 到指定error頁面 ?
? ?} ?
? ?return "success"; ?
} ?
?
如果按照上面的方式處理異常以后,我們用戶最后看到的頁面可能就會是下面這種形式(我想這種錯誤
提示應該稍微友好點了吧):
?
然后我們回到剛才處理異常的地方,如果大家積累了一些項目經驗以后會發現使用上面那種處理異常的
方式可能還不夠靈活:
①因為spring把大多數非運行時異常都轉換成運行時異常(RuntimeException)最后導致程序員根本不
知道什么地方應該進行try...catch操作
②每個方法都重復寫try...catch,而且catch塊內的代碼都很相似,這明顯做了很多重復工作而且還很
容易出錯,同時也加大了單元測試的用例數(項目經理通常喜歡根據代碼行來估算UT case)
③發生異常有很多種情況:可能有數據庫增刪改查錯誤,可能是文件讀寫錯誤,等等。用戶覺得每次發
生異常都是“訪問過程中產生錯誤,請重試”的提示完全不能說明錯誤情況,他們希望讓異常信息更詳
盡些,比如:在執行數據刪除時發生錯誤,這樣他們可以更準確地給維護人員提供bug信息。
?
如何解決上面的問題呢?我是這樣做的:JDK異常或自定義異常+異常攔截器
struts2攔截器的作用在網上有很多資料,在此不再贅述,我的異常攔截器原理如下圖所示:
?首先我的action類、service類和dao類如果有必要捕獲異常,我都會try...catch,catch塊內不記錄
log,通常是拋出一個新異常,并且注明錯誤信息:
Java代碼 ?收藏代碼
//action層執行數據添加操作 ?
public String save(){ ?
? ?try{ ?
? ? ? ? ?//調用service的save方法 ?
? ? ? ? ?service.save(obj); ?
? ?}catch(Exception e){ ?
? ? ? //你問我為什么拋出Runtime異常?因為我懶得在方法后寫throws ?xx ?
? ? ? throw new RuntimeException("添加數據時發生錯誤!",e); ?
? } ?
? ?return "success"; ?
} ?
??
然后在異常攔截器對異常進行處理,看下面的代碼:
Java代碼 ?收藏代碼
public String intercept(ActionInvocation actioninvocation) { ?
??
? ? ? ? String result = null; // Action的返回值 ?
? ? ? ? try { ?
? ? ? ? ? ? // 運行被攔截的Action,期間如果發生異常會被catch住 ?
? ? ? ? ? ? result = actioninvocation.invoke(); ?
? ? ? ? ? ? return result; ?
? ? ? ? } catch (Exception e) { ?
? ? ? ? ? ? /**?
? ? ? ? ? ? ?* 處理異常?
? ? ? ? ? ? ?*/ ?
? ? ? ? ? ? String errorMsg = "未知錯誤!"; ?
? ? ? ? ? ? //通過instanceof判斷到底是什么異常類型 ?
? ? ? ? ? ? if (e instanceof BaseException) { ?
? ? ? ? ? ? ? ? BaseException be = (BaseException) e; ?
? ? ? ? ? ? ? ? be.printStackTrace(); //開發時打印異常信息,方便調試 ?
? ? ? ? ? ? ? ? if(be.getMessage()!=null||Constants.BLANK.equals(be.getMessage().trim())){ ?
? ? ? ? ? ? ? ? ? ? //獲得錯誤信息 ?
? ? ? ? ? ? ? ? ? ? errorMsg = be.getMessage().trim(); ?
? ? ? ? ? ? ? ? } ?
? ? ? ? ? ? } else if(e instanceof RuntimeException){ ?
? ? ? ? ? ? ? ? //未知的運行時異常 ?
? ? ? ? ? ? ? ? RuntimeException re = (RuntimeException)e; ?
? ? ? ? ? ? ? ? re.printStackTrace(); ?
? ? ? ? ? ? } else{ ?
? ? ? ? ? ? ? ? //未知的嚴重異常 ?
? ? ? ? ? ? ? ? e.printStackTrace(); ?
? ? ? ? ? ? } ?
? ? ? ? ? ? //把自定義錯誤信息 ?
? ? ? ? ? ? HttpServletRequest request = (HttpServletRequest) actioninvocation ?
? ? ? ? ? ? ? ? ? ? .getInvocationContext().get(StrutsStatics.HTTP_REQUEST); ?
? ? ? ? ? ? ??
? ? ? ? ? ? /**?
? ? ? ? ? ? ?* 發送錯誤消息到頁面?
? ? ? ? ? ? ?*/ ?
? ? ? ? ? ? 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判斷異常類型的時候一定要從子到父依次找,比如BaseException繼承
與RuntimeException,則必須首先判斷是否是BaseException再判斷是否是RuntimeException。
??
最后在error JSP頁面顯示具體的錯誤消息即可:
Java代碼 ?收藏代碼
<body> ?
<s:if test="%{#request.errorMsg==null}"> ?
? ? <p>對不起,系統發生了未知的錯誤</p> ?
</s:if> ?
<s:else> ?
? ? <p>${requestScope.errorMsg}</p> ?
</s:else> ?
</body> ?
?
以上方式可以攔截后臺代碼所有的異常,但如果出現數據庫連接異常時不能被捕獲的,大家可以使用
struts2的全局異常處理機制來處理:
Java代碼 ?收藏代碼
<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> ?
?
上面這是一個很簡單的異常攔截器,大家可以使用自定義異常,那樣會更靈活一些。
?
以上異常攔截器可以使用其它很多技術替換:比如spring aop,servlet filter等,根據項目實際情況
處理。
?
【補充】ajax也可以進行攔截,但是因為ajax屬于異步操作,action通過response形式直接把數據返回
給ajax回調函數,如果發生異常,ajax是不會執行頁面跳轉的,所以必須把錯誤信息返回給回調函數,
我針對json數據的ajax是這樣做的:
Java代碼 ?收藏代碼
/**?
?* 讀取文件,獲取對應錯誤消息?
?*/ ?
HttpServletResponse response = (HttpServletResponse)actioninvocation.getInvocationContext
().get(StrutsStatics.HTTP_RESPONSE); ?
response.setCharacterEncoding(Constants.ENCODING_UTF8); ?
/**?
?* 發送錯誤消息到頁面?
?*/ ?
PrintWriter out; ?
try { ?
? ? out = response.getWriter(); ?
? ? Message msg = new Message(errorMsg); ?
? ? //把異常信息轉換成json格式返回給前臺 ?
? ? out.print(JSONObject.fromObject(msg).toString()); ?
} catch (IOException e1) { ?
? ? throw e; ?
} ?
?
========
總結
以上是生活随笔為你收集整理的java异常处理学习总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 树学习总结
- 下一篇: 图解用RadASM开发一个同时编辑多个文