Java異常處理設計(二)

Zollty發表於2013-11-19

考慮對JDK的底層堆疊資訊進行處理,一種是重寫JDK的Throwable,另一種是在原錯誤堆疊資訊上進行“二次加工”。目前這兩種方式我都實現了,效果非常好。

 

這篇文章主要記錄對錯誤堆疊進行“二次加工”的實現過程。

 

從大量的實際錯誤日誌分析出發:

 

首先,追根溯源,找到錯誤是從哪個地方new出來的。

例如
com.jfinal.plugin.activerecord.ActiveRecordException: java.lang.ClassCastException: com.alibaba.druid.sql.visitor.SQLEvalVisitorImpl cannot be cast to com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor
 at com.jfinal.plugin.activerecord.Db.paginate(Db.java:554)
 at com.jfinal.plugin.activerecord.Db.paginate(Db.java:593)
 at com.simple.sqpt.services.FWCXService.doGetHouseInfo(FWCXService.java:172)

那麼是在Db.paginate(Db.java:554)這一行new出來的,後面有:
Caused by: java.lang.ClassCastException: com.alibaba.druid.sql.visitor.SQLEvalVisitorImpl cannot be cast to com.alibaba.druid.sql.dialect.sqlserver.visitor.SQLServerASTVisitor
 at com.alibaba.druid.sql.dialect.sqlserver.ast.SQLServerSelectQueryBlock.accept0(SQLServerSelectQueryBlock.java:39)
 at com.alibaba.druid.sql.ast.SQLObjectImpl.accept(SQLObjectImpl.java:40)
說明這個new出來的錯誤,是包裹了另外一個錯誤,可能是有new ActiveRecordException(e);然後這個e是一個ClassCastException型別錯誤
因為只有一個Caused by,可以肯定,這個(ClassCastException) e 是源頭,是從:
SQLServerSelectQueryBlock.accept0(SQLServerSelectQueryBlock.java:39)這裡new出來的。

應用的棧資訊可能夾在中間,比如:

org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is com.ibm.websphere.ce.cm.StaleConnectionException: The Network Adapter could not establish the connectionDSRA0010E: SQL State = 61000, Error Code = 20
  at org.springframework.jdbc.datasource.DataSourceUtils.getConnection(DataSourceUtils.java:82)
  at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:577)
  at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:641)
  at org.springframework.jdbc.core.JdbcTemplate.queryForInt(JdbcTemplate.java:759)
  at com.proj.webc.persist.framework.BaseDAO.queryForCount(BaseDAO.java:142)
  at com.proj.webc.persist.office.dao.OfficeDAO.queryBackUsersCountByCondition(OfficeDAO.java:734)
  at com.proj.webc.logic.office.OfficeManagerImpl.queryCountOfficeByCondition(OfficeManagerImpl.java:79)
  at com.proj.webc.present.orderquery.action.FrontOrderQueryAction.doAction(FrontOrderQueryAction.java:118)
  at com.proj.webc.framework.BaseAction.execute(BaseAction.java:205)
  at sun.reflect.GeneratedMethodAccessor81.invoke(Unknown Source)
  at java.lang.reflect.Method.invoke(Method.java:600)
  at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:450)
  at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:289)

像這種,其實我們只能操縱應用之前的錯誤資訊,應用之後的,超出了我們的管理範圍。 比如上面這個例子,在com.proj.**之前的org.springframework.**是我們可以管理的, 但是後面的sun.reflect.**,java.lang.**,com.opensymphony.**都超出了應用的可視範圍。 (除非從源頭控制,比如com.opensymphony.**的原始碼,最上層,應該是webContainer,例如websphere下,最外層的棧資訊都是com.ibm.**開頭)

分析上面的錯誤資訊,我們可以操作的最外層,是 com.proj.webc.framework.BaseAction.execute(BaseAction.java) 我們可以在這個地方做工作,把它管轄之下的錯誤資訊進行整理,例如可以把 org.springframework.**這種資訊都過濾掉。

如果是用log工具,我們就有控制權。

綜上分析,對於最上層,緊跟著錯誤資訊的地方,只需要追溯到錯誤new出來之前的前幾個軌跡即可。 一般來說,只需要跟蹤和new出來錯誤的那個類在同一個api包之中的,比如上面這個例子, 只需要跟蹤到第二行 com.jfinal.plugin.activerecord.Db.paginate(Db.java:593)即可。

對於Caused by,是非常核心的錯誤出處,一般是在應用之中的,可採取和上面一樣的處理方式,如果是呼叫第三方api丟擲的錯誤,如上面那個例子。 其實捕獲棧資訊的意義不是很大,但是也可以少量捕捉一些,比如一前一尾。但是尾巴不好判斷,所以考慮到效能,只捕捉前面2行就夠意思了。後面的話 要逐行判斷,如果是本應用的類,則捕捉。

捕捉到最後,是三個點:  ... 39 more 這樣,Caused by就處理完成了。如果後面還有Caused by,也用該方法繼續捕捉。

綜上,其實就一個方法: 檢測非\tat開頭的字串,記錄其資訊。(可設定長度大小,比如小於1000)

對於\tat開頭的字串,判斷是否為本應用+特定api的字首,是則保留。

特殊處理: 保留緊跟著非\tat開頭的字串的2行棧資訊,

保留出現本應用+特定api字首那一行的前3行資訊。

演算法複雜度,假設有100行,一行比較30次char(用startWith),總共for迴圈100次,每次比較一個startWith,

每次迴圈假設還做一個5個if的判斷,那麼一共比較的次數為500+3000=3500,對CPU來說,應該不成問題。

 

 

 

相關文章