struts2 異常處理3板斧


板斧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>
View Code

這樣,如果輸入不存在的.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>
View Code

注: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>
View Code

 

板斧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);
    }
}
View Code

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     }
View Code

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 }
View Code

解釋一下:

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>
View Code

運行后,會生成二個日志文件,類似下圖:(業務日志記錄在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>
View Code

解釋一下:
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>
View Code

這樣運行時,就會顯示給用戶一個很"低俗但通用"的錯誤頁面:

顯然,直接把底層異常展示給用戶是不好的做法(相當於直接告訴別人,今天你底褲的顏色),可以稍微裝一下筆,只要改下攔截器:

 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 }
View Code

這樣,用戶看到的信息就變了(當然,實際應用中,下面這個頁面,建議請藝術大師美化一下)

當然,也可以改變攔截器的返回string,比如業務錯誤,返回"biz-error",定位到業務錯誤的專用展示頁面,常規錯誤返回"sys-error",返回 另一個專用錯誤處理頁面(對應的struts.xml的全局錯誤配置也要相應修改)

 

小結:

經過以上處理,常見的異常(錯誤),比如:404/500、action路徑不對、運行異常、業務異常等,即分門別類記錄了詳細日志(便於日后分析),也轉換為友好信息提示給用戶,同時還保證了系統健壯性。最后,對於程序員更重要的是,不用手動寫try/catch之類的代碼了,干活更輕松 (媽媽再也不擔心我的異常了)

 

附:ajax的統一異常處理,請移步 Struts2、Spring MVC4 框架下的ajax統一異常處理


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM