背景环境:1)框架:ssm(struts2.3.16+spring3.2.5+mybatis3.2.6)
2)自定义异常类,必要时刻抛出,我的情况是,在每个页面判断权限(防止有人直接输入页面进入),无权限的话直接抛出无权限异常(自定义的)
3)struts.xml中配置如下:
<global-results>
<result name="login">/login.jsp</result>
<result name="error">/error.jsp</result>
<result name="prompt">/prompt-page.jsp</result>
<result name="no-permission-exception">/no-permission-exception.jsp</result>
</global-results>
<global-exception-mappings>
<!-- 当系统抛出Exception异常时,转入名为exception的结果。 -->
<exception-mapping exception="XX.XX.XX.NoPermissionException" result="no-permission-exception" />//自定义异常
<exception-mapping exception="java.lang.Exception" result="error" />
</global-exception-mappings>
现象描述:每次跳转页面都是error.jsp,说明没有匹配自定义异常。
解决过程:
1)debug跟踪,跟踪到了struts的拦截器ExceptionMappingInterceptor(位置:xwork-core\src\main\java\com\opensymphony\xwork2\interceptor),大体看了一下变量,发现会取出我global中列出的两个异常列表,然后抛出的异常进行比较(JasperException,由servlet异常引起的,detaiMessage中有我的自定义异常,cause中也有我的自定义异常)。当我debug结束的时候发现并没有匹配我的自定义异常。纠结中。。。只能看看到底是如何比较的,为啥把自己定义的没匹配到。
2)自定义异常类,必要时刻抛出,我的情况是,在每个页面判断权限(防止有人直接输入页面进入),无权限的话直接抛出无权限异常(自定义的)
3)struts.xml中配置如下:
<global-results>
<result name="login">/login.jsp</result>
<result name="error">/error.jsp</result>
<result name="prompt">/prompt-page.jsp</result>
<result name="no-permission-exception">/no-permission-exception.jsp</result>
</global-results>
<global-exception-mappings>
<!-- 当系统抛出Exception异常时,转入名为exception的结果。 -->
<exception-mapping exception="XX.XX.XX.NoPermissionException" result="no-permission-exception" />//自定义异常
<exception-mapping exception="java.lang.Exception" result="error" />
</global-exception-mappings>
现象描述:每次跳转页面都是error.jsp,说明没有匹配自定义异常。
解决过程:
1)debug跟踪,跟踪到了struts的拦截器ExceptionMappingInterceptor(位置:xwork-core\src\main\java\com\opensymphony\xwork2\interceptor),大体看了一下变量,发现会取出我global中列出的两个异常列表,然后抛出的异常进行比较(JasperException,由servlet异常引起的,detaiMessage中有我的自定义异常,cause中也有我的自定义异常)。当我debug结束的时候发现并没有匹配我的自定义异常。纠结中。。。只能看看到底是如何比较的,为啥把自己定义的没匹配到。
2)下载Struts2.3.16的开源包,关联继续调试,终于真相大白:截取一下ExceptionMappingInterceptor中的部分源码如下:
@Override
public String intercept(ActionInvocation invocation) throws Exception {
String result;
try {
result = invocation.invoke();
} catch (Exception e) {
if (isLogEnabled()) {
handleLogging(e);
}
List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e);
if (mappingConfig != null && mappingConfig.getResult()!=null) {
Map parameterMap = mappingConfig.getParams();
// create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable
invocation.getInvocationContext().setParameters(new HashMap<String, Object>(parameterMap));
result = mappingConfig.getResult();
publishException(invocation, new ExceptionHolder(e));
} else {
throw e;
}
}
return result;
}
protected ExceptionMappingConfig findMappingFromExceptions(List<ExceptionMappingConfig> exceptionMappings, Throwable t) {
<span style="white-space:pre"> </span>ExceptionMappingConfig config = null;
// Check for specific exception mappings.
if (exceptionMappings != null) {
int deepest = Integer.MAX_VALUE;
for (Object exceptionMapping : exceptionMappings) {
ExceptionMappingConfig exceptionMappingConfig = (ExceptionMappingConfig) exceptionMapping;
int depth = getDepth(exceptionMappingConfig.getExceptionClassName(), t);
if (depth >= 0 && depth < deepest) {
deepest = depth;
config = exceptionMappingConfig;
}
}
}
return config;
}
public int getDepth(String exceptionMapping, Throwable t) {
return getDepth(exceptionMapping, t.getClass(), 0);
}
private int getDepth(String exceptionMapping, Class exceptionClass, int depth) {
if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
// If we've gone as far as we can go and haven't found it...
if (exceptionClass.equals(Throwable.class)) {
return -1;
}
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
}
从这个拦截器的源码中可以看到整个流程:先取到配置文件中的异常列表,然后把抛出的异常跟这个列表中的异常作比较,这个逻辑在上一次的debug已经认证了,主要是是匹配的这个段代码
<span style="white-space:pre"> </span>if (exceptionClass.getName().contains(exceptionMapping)) {
// Found it!
return depth;
}
首先判断异常类的名字跟我们配置文件中取到的类名是否一致,如果一致ok,如果不一致继续判断
return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
这段代码告诉了我们如何判断,直接取我们抛出的异常的上级进行判断,这样循环,直接到最顶级的父级进行判断。显然我们抛出的jasperException不可能出现在这里,从
return getDepth(exceptionMapping, t.getClass(), 0);
这段代码已经注定了不能匹配成功了,直接把我们抛出的异常转化为了class,使得我们的其它信息已经丢失。
解决思路:
1.我的第一印象修改源代码- -!后来一想,这个属于下策,可能其它地方我们有注意到,导致我自己的失误,所以想其他方法
2.网络是个好东西,看了一下网上的这方面的资料,倾向于自定义的拦截器,毕竟我调试的源码也是自带的拦截器,为何不自己也定义一个拦截器进行修正呢。
3.修改之后的struts.xml如下:
<interceptors>
<span style="white-space:pre"> </span><interceptor name="MyCustomInterceptor" class="cn.com.cottech.cotton.Interceptor.MyCustomInterceptor"></interceptor>
<span style="white-space:pre"> </span><!--这里可以配置多个interceptor-->
<span style="white-space:pre"> </span><!--拦截器栈-->
<span style="white-space:pre"> </span><interceptor-stack name="MyInterceptorStack">
<span style="white-space:pre"> </span><interceptor-ref name="defaultStack" />
<span style="white-space:pre"> </span><interceptor-ref name="MyCustomInterceptor"></interceptor-ref>
<span style="white-space:pre"> </span><!--这里也可以配置多个interceptor-ref-->
<span style="white-space:pre"> </span></interceptor-stack>
<span style="white-space:pre"> </span></interceptors>
<span style="white-space:pre"> </span><!-- 全局默认拦截器 ,一个系统中只能一个全局默认-->
<span style="white-space:pre"> </span><default-interceptor-ref name="MyInterceptorStack"></default-interceptor-ref>
<span style="white-space:pre"> </span><!-- 设置全局的返回结果 -->
<span style="white-space:pre"> </span><global-results>
<span style="white-space:pre"> </span><result name="login">/login.jsp</result>
<span style="white-space:pre"> </span><result name="error">/error.jsp</result>
<span style="white-space:pre"> </span><result name="prompt">/prompt-page.jsp</result>
<span style="white-space:pre"> </span><result name="no-permission-exception">/no-permission-exception.jsp</result>
<span style="white-space:pre"> </span></global-results>
里面的一些定义就不解释了,这里注意一下,拦截器的顺序,可以看到有两个拦截器,一个是系统默认的,还有一个自定义的,默认的必须有,它牵扯到struts整个路程的各种处理,比如赋值,request,session,form的值等等。
我的自定义的部分代码如下:
@Override
<span style="white-space:pre"> </span>public String intercept(ActionInvocation ac) throws Exception {
<span style="white-space:pre"> </span>String result = "error";
<span style="white-space:pre"> </span>try {
<span style="white-space:pre"> </span>result = ac.invoke();
<span style="white-space:pre"> </span>} catch (Exception e) {
<span style="white-space:pre"> </span>log.debug(e, e);
<span style="white-space:pre"> </span>e.printStackTrace();
<span style="white-space:pre"> </span>// 这里可以有多个catch,但要注意顺序问题,用于捕捉不同的异常,进行不同的处理
<span style="white-space:pre"> </span>List<String> exceptionList = new ArrayList<String>();
<span style="white-space:pre"> </span>exceptionList.add(NoPermissionException.class.getName()+",no-permission-exception");
<span style="white-space:pre"> </span>exceptionList.add(NullPointerException.class.getName()+",error");
<span style="white-space:pre"> </span>int deepest = Integer.MAX_VALUE;
<span style="white-space:pre"> </span>for(int i=0;i<exceptionList.size();i++){
<span style="white-space:pre"> </span>String[] exception_con = exceptionList.get(i).split(",");
<span style="white-space:pre"> </span>String exceptionName = exception_con[0];
<span style="white-space:pre"> </span>if(isContainException(exceptionName, e)){
<span style="white-space:pre"> </span>return exception_con[1];
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>return result;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>
<span style="white-space:pre"> </span> public boolean isContainException(String exceptionMapping, Throwable t) {
<span style="white-space:pre"> </span> <span style="white-space:pre"> </span>int depth = 1;
<span style="white-space:pre"> </span>if(t.getClass().getName().contains(exceptionMapping)){//直接判断异常的名称是什么
<span style="white-space:pre"> </span>return true;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>Throwable tb = t.getCause();
<span style="white-space:pre"> </span>while(tb!=null){//如果异常名称不符合,那就查看他的起因
<span style="white-space:pre"> </span>if(tb.getMessage().contains(exceptionMapping)){
<span style="white-space:pre"> </span>return true;//if the cause contains exceptionMapping,return depth,else invoke getDepth(String exceptionMapping, Class exceptionClass, int depth)
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>tb = tb.getCause();
<span style="white-space:pre"> </span>if(depth>=Integer.MAX_VALUE){//Prevent loop
<span style="white-space:pre"> </span>break;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>++depth;
<span style="white-space:pre"> </span>}
<span style="white-space:pre"> </span>return false;
//return getDepth(exceptionMapping, t.getClass(), 0);
}
自定义的拦截器主要功能有两个:
1)打印异常,出现异常说明程序有问题,这个是需要程序员进行跟踪修改的。
2)主要处理我自定义的权限异常,将来可以添加系统的和自定义的其它异常。
备注:这里的异常是写死的,可以参考源码,直接从config配置中取。这里就不修改了
到此这个异常体系基本建立