板斧1:找不到action的错误
在struts.xml中参考如下配置
代码语言:javascript复制 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中
代码语言:javascript复制 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搞定
代码语言:javascript复制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意思一下)
代码语言:javascript复制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中,直接向外抛异常即可
代码语言:javascript复制 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的方法执行会先经常拦截器,所以拦截器是处理异常的好机机(比如:记录异常到日志文件、转换成友好异常信息)
代码语言:javascript复制 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 }
解释一下:
代码语言:javascript复制ai.getStack().push(new ExceptionHolder(e)); 这一行的用途是将异常信息放入stack,这样后面的异常处理页面,就能显示异常详细信息
上面只是演示,将"业务异常MyException"与"常规异常Exception"分开处理,并且用不同的Logger实例来记录,这样就能将"业务异常"与"常规异常"分别记到不同的log文件中,对应的logback.xml参考配置:
代码语言:javascript复制 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中拦截器配置,以及全局异常处理页面
代码语言:javascript复制 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 代码
代码语言:javascript复制 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>
这样运行时,就会显示给用户一个很"低俗但通用"的错误页面:
显然,直接把底层异常展示给用户是不好的做法(相当于直接告诉别人,今天你底裤的颜色),可以稍微装一下笔,只要改下拦截器:
代码语言:javascript复制 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统一异常处理