struts2 18拦截器详解(二)
exception拦截器对应的类为com.opensymphony.xwork2.interceptor.exceptionmappinginterceptor 该拦截器是struts2专门用于处理异常的,如果我们进行了异常配置,当struts2的执行出现异常后可以跳转到指定的result,这也是struts2的异常处理机制。
下面看一下该拦截器的:
[java]
public class exceptionmappinginterceptor extends abstractinterceptor {
//省略...
@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();
//从异常映射获取result
string mappedresult = this.findresultfromexceptions(exceptionmappings, e);
if (mappedresult != null) {
result = mappedresult;//如果获取的result不为null则替换原来的result
publishexception(invocation, new exceptionholder(e));//发布异常
} else {
throw e;
}
}
return result;//返回result
}
//省略...
}
限于篇幅,这里只列出了其最重要的intercept方法,等用到其它方法的时候再列出。可以看到该拦截器继承自abstractinterceptor,abstractinterceptor实现了interceptor接口,abstractinterceptor很简单,就是对interceptor接口中声明的init与destroy方法进行了空实现,这样做的好处理是如果我们自己写的拦截器继承自abstractinterceptor就不用去实现init与destroy方法了,如果需要这两个方法只要进行覆盖即可,这样比较方便,仅此而已。
该拦截器比较特殊,之所以说其特殊是因为其在defaultstack中所处的位置还有其intercept方法中invocation.invoke()执行的地方。exception拦截器处于defaultstack中第一个位置,表面上是第一个位置,但大家可以看到result = invocation.invoke();前面没有任何逻辑代码,而是直接执行下一个拦截器,加上越后执行的拦截器越先执行完成返回(如果该结论不清楚请参看struts2 18拦截器详解(一) ),这样就将导致result = invocation.invoke();后面的代码是在其它所有拦截器执行完成后才会执行。struts2这样做的原因是由该拦截器的功能所决定的。因为该拦截器的目的是要对异常进行处理,而异常可以出现在每一个拦截器中,所以集中处理异常的代码要放到最后执行,所以这就决定了该拦截器要放置到defaultstack的第一个位置,并且在intercept方法中直接调用下一个拦截器。在struts2中可以定义多个拦截器栈,其实不管理是哪一个拦截器栈,exception拦截器都应该是处理第一个位置。
result = invocation.invoke();这里虽然只有一句,其实是包含了该拦截器后面的所有拦截器包括action的执行,如果在执行的过程中抛出异常则会进行到catch块中,首先获取异常映射配置信息,然后从异常映射配置信息查找result,如果找到了则将原来的result进行替换,这样返回的result就会指我们在配置文件中配置的异常result,以达到处理异常的目的。
下面是findresultfromexceptions方法的源码:
[java]
protected string findresultfromexceptions(list<exceptionmappingconfig> exceptionmappings, throwable t) {
string result = 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;
result = exceptionmappingconfig.getresult();
}
}
}
return result;
}
getdepth方法源码:
[java]
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)) {//如果已经检索到了throwable则返回-1
return -1;
}
return getdepth(exceptionmapping, exceptionclass.getsuperclass(), depth + 1);
}
findresultfromexceptions方法中传入了异常映射配置信息,也就是在struts2配置文件中的<exception-mapping result="" exception=""/>节点,因为我们可能配置多个<exception-mapping result="" exception=""/>节点,所以传进来的是一个list,如果不为null则对其进行迭代。int depth = getdepth(exceptionmappingconfig.getexceptionclassname(), t);这句代码用于获取一个深度值,这句代码加上上下面的if语句判断决定了哪一种异常指向哪一个result。
首先声明了一个名为deepest整形变量,初始值为integer.max_value(这只是个初始值,该值会在后面迭代的过程被修改),在迭代的过程中,第一次代码depth为0,如果在拦截器与action的执行过程中抛出的异常匹配到了我们配置的异常则会反这个深度值,如果没有匹配到则会继续匹配该抛出的异常类的父类,并且深度值加1,即这句代码:getdepth(exceptionmapping, exceptionclass.getsuperclass(), depth + 1);这里使用了递归,其退出条件是匹配到了相应的异常,或者检索到了throwable类,因为所有异常都继承自throwable,如果检索到了throwable类就没必要再往上检索了,直接返回-1(即没有匹配到配置的异常),获取到了depth深度值后执行if条件判断,这个判断很重要,我们特意将它拿过来:
[java]
if (depth >= 0 && depth < deepest) {
deepest = depth;
result = exceptionmappingconfig.getresult();
}
如果depth>=0&&depth<deepest则会执行if语句块中的代码,depth>=0则表示这次迭代过程中匹配到了我们配置的异常,第一次迭代depth<deepest是肯定成立的,所以第一次迭代如果匹配上了就会执行if语句块中的代码,代码中将deepest修改为了上一次返回的depth值并对result变量使用异常映射中的result进行赋值。如果没有匹配到我们配置的异常则result不会被赋值。在后面的迭代过程如果又匹配上了进行if判断的时候应该注意,这时的deepest值是上一次匹配返回的深度值,如果这次返回的深度值比上一次返回的深度值小才会执行if语句块中的代码,深度值是通过递归调用得到了也就是depth深度值越小则该异常越具体,因为匹配父异常的时候深度自加1,所以当抛出的异常匹配上多个我们在配置文件中配置的异常映射的时候struts2会选择最具体的异常映射。举个例子,下面是异常配置:
[html]
<global-exception-mappings>
<exception-mapping result="error1" exception="java.lang.exception"/>
<exception-mapping result="error2" exception="java.lang.indexoutofboundsexception"/>
</global-exception-mappings>
这里我们配置了两个异常映射,如果在拦截器与action的执行过程过程中抛出的是indexoutofboundsexception则即会匹配第一个异常映射也会匹配第二个异常映射,但是第二个更具体即返回的深度值更小,所以在抛出indexoutofboundsexception的时候struts2会选择error2这个result。
现在回到该拦截器的intercept方法中。如果findresultfromexceptions方法匹配到了我们配置的异常则会返回异常映射中的result值,如果没有匹配上则返回null,如果不为null的话,将原来的result值替换为我们异常映射中的result值并发布异常,如果为null则返回原来的result,在页面上就会显示出异常栈信息。这样如果匹配上了出来了异常到了前台显示的时候就会显示我们指定的页面。
下面看一下publishexception(invocation, new exceptionholder(e));方法所做的工作:
[java]
protected void publishexception(actioninvocation invocation, exceptionholder exceptionholder) {
invocation.getstack().push(exceptionholder);
}
该方法很简单,就是将抛出的异常包装进了一个叫exceptionholder类型的对象中,然后压入了值栈的栈顶,这样我们就可以是视图中获取异常信息(如果需要的话)。
最后讲一下匹配异常的细节,就是:if (exceptionclass.getname().contains(exceptionmapping))这个if条件判断,这样struts2判断有无匹配上用的是contains方法,即我们在配置异常映射的时候不需要将异常类名写全也可以,比如:
<exception-mapping result="error2" exception="java.lang.indexoutofboundsexception"/>我们配置成
<exception-mapping result="error2" exception="java.lang.indexoutof"/>也可以匹配上indexoutofboundsexception,这里个人觉得更好的做法应该是将所抛出的异常类的类名与配置异常映射判断是否相等,这样才更严谨,即contains方法改为equals方法。