板斧1:找不到action的錯誤
在struts.xml中參考如下配置

1 <struts> 2 3 ... 4 <package name="default" namespace="/" extends="struts-default"> 5 6 ... 7 8 <default-action-ref name="index" /> 9 10 ... 11 12 <action name="index"> 13 <result type="redirectAction"> 14 <param name="actionName">HelloWorld</param> 15 <param name="namespace">/home</param> 16 </result> 17 </action> 18 19 </package> 20 21 <include file="struts-home.xml" /> 22 23 </struts>
這樣,如果輸入不存在的.action 路徑,會直接重定向到index這個Action上,而index中指定的HelloWorld這個Action,在struts-home.xml中

1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" 4 "http://struts.apache.org/dtds/struts-2.0.dtd"> 5 <struts> 6 7 <package name="home" namespace="/home" extends="default"> 8 9 <action name="HelloWorld_*" method="{1}" class="HelloWorldAction"> 10 <result>/WEB-INF/views/home/HelloWorld.jsp</result> 11 </action> 12 13 </package> 14 </struts>
注:struts.xml中節點出現的順序,是有嚴格約定的,如果弄錯順序了,啟動時,就會看到類似下面的異常
org.xml.sax.SAXParseException: The content of element type "package" must match
"(result-types?,interceptors?,default-interceptor-ref?,default-action-ref?,default-class-ref?,global-results?,global-exception-mappings?,action*)".
即各節點的順序為:
result-types -> interceptors -> default-interceptor-ref -> default-action-ref -> default-class-ref -> global-results -> global-exception-mappings -> action
板斧2:404/500之類的常規錯誤
呃,這個struts2處理不了,得靠web.xml搞定

1 <error-page> 2 <error-code>404</error-code> 3 <location>/WEB-INF/common/error/404.jsp</location> 4 </error-page> 5 6 <error-page> 7 <error-code>500</error-code> 8 <location>/WEB-INF/common/error/500.jsp</location> 9 </error-page>
板斧3:業務異常/常規(運行)異常
a) 定義業務異常 (這里簡單弄一個土鱉的MyException意思一下)

package com.cnblogs.yjmyzz.exception; public class MyException extends Exception { private static final long serialVersionUID = -8315871537638142775L; public MyException() { super(); } public MyException(String message) { super(message); } }
b) Action中,直接向外拋異常即可

1 public String execute() throws Exception, MyException { 2 3 //testException(); 4 5 testMyException(); 6 7 return SUCCESS; 8 } 9 10 /*private void testException() throws Exception { 11 throw new Exception("normal exception"); 12 }*/ 13 14 private void testMyException() throws MyException { 15 throw new MyException("my exception"); 16 }
c) 定義攔截器,處理異常
struts2中所有action的方法執行會先經常攔截器,所以攔截器是處理異常的好機機(比如:記錄異常到日志文件、轉換成友好異常信息)

1 package com.cnblogs.yjmyzz.Interceptor; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import com.cnblogs.yjmyzz.exception.MyException; 7 import com.opensymphony.xwork2.ActionInvocation; 8 import com.opensymphony.xwork2.interceptor.*; 9 10 public class ExceptionInterceptor extends AbstractInterceptor { 11 12 private static final long serialVersionUID = -6827886613872084673L; 13 protected Logger logger = LoggerFactory.getLogger(this.getClass()); 14 protected Logger myexLogger = LoggerFactory.getLogger("my-exception"); 15 16 @Override 17 public String intercept(ActionInvocation ai) throws Exception { 18 String result = null; 19 try { 20 logger.debug("ExceptionInterceptor.intercept() is called!"); 21 result = ai.invoke(); 22 } catch (MyException e) { 23 // 捕獲自定義異常 24 myexLogger.error(ai.toString(), e); 25 ai.getStack().push(new ExceptionHolder(e)); 26 result = "error"; 27 } catch (Exception e) { 28 // 其它異常 29 logger.error(ai.toString(), e); 30 ai.getStack().push(new ExceptionHolder(e)); 31 result = "error"; 32 } 33 return result; 34 } 35 36 }
解釋一下:
ai.getStack().push(new ExceptionHolder(e)); 這一行的用途是將異常信息放入stack,這樣后面的異常處理頁面,就能顯示異常詳細信息
上面只是演示,將"業務異常MyException"與"常規異常Exception"分開處理,並且用不同的Logger實例來記錄,這樣就能將"業務異常"與"常規異常"分別記到不同的log文件中,對應的logback.xml參考配置:

1 <?xml version="1.0" encoding="UTF-8" ?> 2 <configuration scan="true" scanPeriod="1800 seconds" 3 debug="false"> 4 5 <property name="USER_HOME" value="logs" /> 6 <property scope="context" name="FILE_NAME" value="test-logback" /> 7 8 <timestamp key="byDay" datePattern="yyyy-MM-dd" /> 9 10 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 11 <encoder> 12 <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 13 </pattern> 14 </encoder> 15 </appender> 16 17 <appender name="file" 18 class="ch.qos.logback.core.rolling.RollingFileAppender"> 19 <file>${USER_HOME}/${FILE_NAME}.log</file> 20 21 <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> 22 <fileNamePattern>${USER_HOME}/${byDay}/${FILE_NAME}-${byDay}-%i.log.zip 23 </fileNamePattern> 24 <minIndex>1</minIndex> 25 <maxIndex>10</maxIndex> 26 </rollingPolicy> 27 28 <triggeringPolicy 29 class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> 30 <maxFileSize>5MB</maxFileSize> 31 </triggeringPolicy> 32 <encoder> 33 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-4relative [%thread] %-5level 34 %logger{150} - %msg%n 35 </pattern> 36 </encoder> 37 </appender> 38 39 40 <appender name="exception-file" 41 class="ch.qos.logback.core.rolling.RollingFileAppender"> 42 <file>${USER_HOME}/${FILE_NAME}_myexception.log</file> 43 44 <rollingPolicy class="ch.qos.logback.core.rolling.FixedWindowRollingPolicy"> 45 <fileNamePattern>${USER_HOME}/${byDay}/${FILE_NAME}-${byDay}-%i.log.zip 46 </fileNamePattern> 47 <minIndex>1</minIndex> 48 <maxIndex>10</maxIndex> 49 </rollingPolicy> 50 51 <triggeringPolicy 52 class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> 53 <maxFileSize>5MB</maxFileSize> 54 </triggeringPolicy> 55 <encoder> 56 <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} %-4relative [%thread] %-5level 57 %logger{150} - %msg%n 58 </pattern> 59 </encoder> 60 </appender> 61 62 <logger name="com.cnblogs.yjmyzz" level="error" additivity="true"> 63 <appender-ref ref="file" /> 64 </logger> 65 66 <logger name="my-exception" level="error" additivity="true"> 67 <appender-ref ref="exception-file" /> 68 </logger> 69 70 <root level="error"> 71 <appender-ref ref="STDOUT" /> 72 </root> 73 </configuration>
運行后,會生成二個日志文件,類似下圖:(業務日志記錄在test-logback_myexception.log中,常規運行異常記錄在test-logback.log中)
tips:如果還有更多的異常類型要處理(比如:SQL異常、Spring異常、網絡連接異常等,參考上面的處理)。另:如果把3.b)中Action方法里的testMyException()注釋掉,換成testException(),即拋出普通異常,則異常信息將記錄到test-logback.log中
d) struts中攔截器配置,以及全局異常處理頁面

1 <?xml version="1.0" encoding="UTF-8" ?> 2 <!DOCTYPE struts PUBLIC 3 "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" 4 "http://struts.apache.org/dtds/struts-2.3.dtd"> 5 6 <struts> 7 8 <constant name="struts.enable.DynamicMethodInvocation" value="false" /> 9 <constant name="struts.devMode" value="true" /> 10 11 <package name="default" namespace="/" extends="struts-default"> 12 13 <interceptors> 14 <interceptor name="myinterceptor" 15 class="com.cnblogs.yjmyzz.Interceptor.ExceptionInterceptor"> 16 </interceptor> 17 18 <interceptor-stack name="myStack"> 19 <interceptor-ref name="myinterceptor" /> 20 </interceptor-stack> 21 </interceptors> 22 23 <default-interceptor-ref name="myStack" /> 24 <default-action-ref name="index" /> 25 26 <global-results> 27 <result name="error">/WEB-INF/common/error.jsp</result> 28 </global-results> 29 30 <global-exception-mappings> 31 <exception-mapping exception="java.lang.Exception" 32 result="error" /> 33 </global-exception-mappings> 34 35 <action name="index"> 36 <result type="redirectAction"> 37 <param name="actionName">HelloWorld</param> 38 <param name="namespace">/home</param> 39 </result> 40 </action> 41 42 </package> 43 44 <include file="struts-home.xml" /> 45 <include file="struts-mytatis.xml" /> 46 47 </struts>
解釋一下:
13-21行,注冊了自定義的攔截器,如果有更多的攔截器,19行后繼續加即可。
23行,指定了默認的攔截器棧
26-28行,指定了全局的error返回頁面
30-33行,指定了處理的異常類型
e) 通用error.jsp 代碼

1 <%@ page contentType="text/html;charset=UTF-8" language="java" %> 2 <%@ taglib prefix="s" uri="/struts-tags" %> 3 4 <html> 5 <head><title>Simple jsp page</title></head> 6 <body> 7 <h3>Exception:</h3> 8 <s:property value="exception"/> 9 10 <h3>Stack trace:</h3> 11 <pre> 12 <s:property value="exceptionStack"/> 13 </pre> 14 </body> 15 </html>
這樣運行時,就會顯示給用戶一個很"低俗但通用"的錯誤頁面:
顯然,直接把底層異常展示給用戶是不好的做法(相當於直接告訴別人,今天你底褲的顏色),可以稍微裝一下筆,只要改下攔截器:

1 package com.cnblogs.yjmyzz.Interceptor; 2 3 import org.slf4j.Logger; 4 import org.slf4j.LoggerFactory; 5 6 import com.cnblogs.yjmyzz.exception.MyException; 7 import com.opensymphony.xwork2.ActionInvocation; 8 import com.opensymphony.xwork2.interceptor.*; 9 10 public class ExceptionInterceptor extends AbstractInterceptor { 11 12 private static final long serialVersionUID = -6827886613872084673L; 13 protected Logger logger = LoggerFactory.getLogger(this.getClass()); 14 protected Logger myexLogger = LoggerFactory.getLogger("my-exception"); 15 16 @Override 17 public String intercept(ActionInvocation ai) throws Exception { 18 String result = null; 19 try { 20 logger.debug("ExceptionInterceptor.intercept() is called!"); 21 result = ai.invoke(); 22 } catch (MyException e) { 23 // 捕獲自定義異常 24 myexLogger.error(ai.toString(), e); 25 // 轉換成友好異常,並放入stack中 26 ai.getStack().push( 27 new ExceptionHolder(new Exception("業務繁忙,讓我喘口氣先!"))); 28 result = "error"; 29 } catch (Exception e) { 30 // 其它異常 31 logger.error(ai.toString(), e); 32 // 轉換成友好異常,並放入stack中 33 ai.getStack().push( 34 new ExceptionHolder(new Exception("系統太累了,需要休息一下!"))); 35 result = "error"; 36 } 37 return result; 38 } 39 40 }
這樣,用戶看到的信息就變了(當然,實際應用中,下面這個頁面,建議請藝術大師美化一下)
當然,也可以改變攔截器的返回string,比如業務錯誤,返回"biz-error",定位到業務錯誤的專用展示頁面,常規錯誤返回"sys-error",返回 另一個專用錯誤處理頁面(對應的struts.xml的全局錯誤配置也要相應修改)
小結:
經過以上處理,常見的異常(錯誤),比如:404/500、action路徑不對、運行異常、業務異常等,即分門別類記錄了詳細日志(便於日后分析),也轉換為友好信息提示給用戶,同時還保證了系統健壯性。最后,對於程序員更重要的是,不用手動寫try/catch之類的代碼了,干活更輕松 (媽媽再也不擔心我的異常了)
附:ajax的統一異常處理,請移步 Struts2、Spring MVC4 框架下的ajax統一異常處理