[问题]
在解难经3:Struts2,拦截器拦不住Result?中,碰到的一个难题,当在PreResultListener中的抛出异常时,总是不能跳转到配好的异常页面去,而是抛出ServletException。换句话说,异常映射拦截器(具体来说指由XWork提供的ExceptionMappingInterceptor),根本拦截不住这种异常。按理说,不应该这样啊,Action里的异常是可以被捕捉并跳转到相应的错误处理页面的,到底是哪里出的问题?
由于春节回湖南老家过年了,这个问题也暂时搁置下来。
[探幽]
过年在家的时候,脑袋里经常回想到这个问题,初步的分析结果是,问题应该出在Struts2和XWork的核心类,以及异常映射拦截器的异常处理这几个地方。
演员表
在分析这个问题之前,有必要介绍一下一些基本情况,下面就对分析过程中,几个即将登场的核心类演员一一介绍(按出场顺序排序),他们在Struts2的每次请求处理中,都扮演重要角色:
0、姓名:过滤分发器(FilterDispacher)
单位:Struts2
职责:初始化分发器,根据请求查找对应的Action映射配置,并调用分发器处理请求,执行Action
1、姓名:分发器(Dispacher)
单位:Struts2
部门:org.struts2.dispacher
职责:接受从过滤器分发过来的请求,并执行对应的Action
2、姓名:动作代理(ActionProxy、StrutsActionProxy)
单位:XWork(ActionProxy)、Struts2(StrutsActionProxy)
职责:负责维持Action配置,调用动作调用器
3、姓名:动作调用器(ActionInvocation、DefaultActionInvocation)
单位:XWork
职责:负责维持Action调用过程中的状态,调用拦截器和最终的Action方法
4、姓名:拦截器(Interceptor、ExceptionMappingInterceptor)
单位:XWork
职责:负责在调用Action方法进行拦截,实现可扩展的功能处理,如异常映射拦截器实现异常时自动跳转到错误页面的功能
5、姓名:动作(Action)
单位:XWork
职责:负责最终的请求业务处理实现
6、姓名:结果(Result)
单位:XWork
职责:负责最终的响应页面的生成
除了上述核心类做主角,在本次解难剧本中,下面的几个跑龙套的家伙也必不可少:
7、姓名:结果前置监听器(PreResultListener、测试用匿名实现类)
单位:XWork(接口)、liuu(匿名测试监听处理类实现)
职责:在Action执行之后,Result执行之前触发该事件监听器,方便应用做出特定的处理;在匿名实现类中,模拟抛出异常
8、姓名:测试拦截器(HelloInterceptor)
单位:liuu
职责:自定义拦截器实现,模拟在特定位置抛出异常
9、姓名:测试动作(HelloAction)
单位:liuu
职责:自定义Action实现,模拟某个业务请求处理
另外,请求(resquest)是贯穿全剧的道具。
接下来,我们布置一下场景:
1、使用Struts2提供的空白项目war包(我用的是struts2-blank-2.0.11.war),导入到Eclipse中作为测试项目
2、创建HelloInterceptor并配置为hello拦截器,可以根据请求参数(如P1)在拦截器中抛出一个异常(E1),
3、在HelloInterceptor中,为ActionInvocation增加一个匿名测试结果前置监听器类,根据请求参数(P2)抛出异常(E2)
4、开发一个空的HelloAction类,配置默认拦截器栈和hello拦截器
6、开发一个正常业务页面hello.jsp,并配置为HelloAction的success结果页面
5、开发一个错误显示页面error.jsp,并配置为全局异常结果页面error
好了,演员悉数登场,场景布置完毕,好戏开锣。
场景一:正常处理请求
场景描述:
1、在Tomcat下启动项目,打开URL:http://localhost:8080/struts2-blank-2.0.11/example/hello.action
2、正常显示hello.jsp页面
执行分析:在请求得到正常处理时,各大旦角各司其责,配合默契,整体非常流畅:
从这个时序图里,我们可以看出,真正的主角,是动作调用器(DefaultActionInvocation),中心方法是invoke:
1、依次遍历调用Action的拦截器栈
2、在栈底,执行Action
3、如果没有执行过(executed,默认为false),则执行:
3.1 查询是否有结果前置监听器,如果有则顺序调用
3.2 如果需要执行过结果Result(默认为true),则执行之
3.3 设置为已执行状态(executed=true)(注意这一点,这是理解本次难经的关键)
场景二、动作执行异常
场景描述:
1、在Tomcat下启动项目,打开URL:http://localhost:8080/struts2-blank-2.0.11/example/hello.action?action
2、action参数触发HelloAction抛出异常,最终跳转到error.jsp页面
场景分析:
如果对场景一理解清楚了,那么,对动作(Action)执行异常时,所发生的一切,应该不难了解:
1、HelloAction抛出异常时,由于里层的拦截器没有catch,因此拦截器层层退栈
2、退到ExceptionMappingInterceptor后异常被catch,在查询全局异常映射配置后,返回调用结果为error
3、后续处理同场景一
场景三:监听器调用异常
场景描述:
1、在Tomcat下启动项目,打开URL:http://localhost:8080/struts2-blank-2.0.11/example/hello.action?inner
2、inner参数将触发HelloInterceptor中的匿名PreResultListener抛出异常
3、但是最终却显示500错误,ServletException异常,而不是跳转到error.jsp页面
场景分析:
现在,问题来了。既然在Action中抛出异常时,可以自动跳转到错误页面,为什么结果前置监听器里抛出的异常时,不能跳转到error.jsp,而是抛出ServletException呢?
来看看奥妙在哪里吧:
发现问题在哪了么:
关键的问题是,在PreResultListener抛出异常后,PreResultListener又被多执行了一次!
我们来看看关键代码,截取自DefaultActionInvocation.invoke:
- if (!executed) {
-
if (preResultListeners != null) {
-
for (Iterator iterator = preResultListeners.iterator();
- iterator.hasNext();) {
- PreResultListener listener = (PreResultListener) iterator.next();
-
-
String _profileKey="preResultListener: ";
-
try {
- UtilTimerStack.push(_profileKey);
-
listener.beforeResult(this, resultCode);
- }
-
finally {
- UtilTimerStack.pop(_profileKey);
- }
- }
- }
-
-
-
if (proxy.getExecuteResult()) {
- executeResult();
- }
-
-
<SPAN style="COLOR: #ff0000">executed = true;</SPAN>
- }
if (!executed) {
if (preResultListeners != null) {
for (Iterator iterator = preResultListeners.iterator();
iterator.hasNext();) {
PreResultListener listener = (PreResultListener) iterator.next();
String _profileKey="preResultListener: ";
try {
UtilTimerStack.push(_profileKey);
listener.beforeResult(this, resultCode);
}
finally {
UtilTimerStack.pop(_profileKey);
}
}
}
// now execute the result, if we're supposed to
if (proxy.getExecuteResult()) {
executeResult();
}
executed = true;
}
原来:
1、在第一次调用PreResultListener时,第一个异常抛出,当前线程退栈;执行不到“executed = true ",execute是false
2、在ExceptionMappingInterceptor捕捉到这个异常后,返回结果码error
3、线程继续退栈,直到退出所有拦截器,然后会重新执行上述代码
4、由于execute还是false,所有PreResultListener被再次执行,于是又抛出第二个异常
5、对于这个新的异常,ExceptionMappingInterceptor已经无能为力,直到Dispachter再次捕捉到这个异常,并转为ServletException抛出
6、Servlet容器catch了这个异常,转到默认异常页面,上面显示的就是Tomcat的默认异常页面
。。。。。。
唉,问题已经完全清楚了,或许,这应该算Struts2(这里是2.0.11)的一个bug,不知道最新版里是否有修正。
[解难]
搞清楚了原因,解决起来那就是手到擒来了:只要让PreResultListener不能再次执行,一切就OK了。
我给匿名PreResultListener实现类增加一个状态字段executed,防止其多次执行:
- final ActionProxy ap = ai.getProxy();
-
ai.addPreResultListener(new PreResultListener() {
-
private boolean executed = false;
-
-
public void beforeResult(ActionInvocation invocation,
- String resultCode) {
-
if <STRONG>(!executed) {
-
if (req.getParameter("fixed") != null)
-
executed = true;</STRONG>
-
-
System.out.println("in pre result listener ");
-
if (req.getParameter("inner") != null) {
-
-
throw new RuntimeException(
-
"exception in intercept befor result in inner class : "
-
+ req.getParameter("inner"));
-
- }
- }
- }
- });
final ActionProxy ap = ai.getProxy();
ai.addPreResultListener(new PreResultListener() {
private boolean executed = false;
public void beforeResult(ActionInvocation invocation,
String resultCode) {
if (!executed) {
if (req.getParameter("fixed") != null)
executed = true;
System.out.println("in pre result listener ");
if (req.getParameter("inner") != null) {
throw new RuntimeException(
"exception in intercept befor result in inner class : "
+ req.getParameter("inner"));
}
}
}
});
场景四:解难
场景描述:
再次部署后,打开URL:http://localhost:8080/struts2-blank-2.0.11/example/hello.action?inner&fixed
霍霍,浏览器乖乖的跳转到了error.jsp页面,整个世界清静了......
场景分析:
如果列为看官大大能坚持看到这里,不妨再看一下解难场景下的时序图:
这应该才是难经3:Struts2,拦截器拦不住Result?问题的最终解。
相关推荐
Struts2拦截器(Interceptor) Struts2拦截器(Interceptor)
在Struts2中,拦截器(Interceptors)扮演着核心角色,增强了框架的功能和灵活性。这篇文章将深入探讨Struts2拦截器的概念、工作原理以及如何在实际应用中使用它们。 **一、什么是Struts2拦截器** 拦截器是基于AOP...
拦截器在Struts2中扮演着至关重要的角色,它们是AOP(面向切面编程)的一个实现,用于在Action执行前后插入额外的功能。下面将详细探讨Struts2拦截器及其工作原理。 ### 一、Struts2 拦截器概念 1. **拦截器是什么...
2. **默认拦截器栈**:`defaultStack`包含了Struts2内置的一些拦截器,如`params`(处理参数),`i18n`(处理国际化),`exception`(处理异常)等。 3. **应用全局拦截器**:现在,所有Action都会在执行前经过`...
- **拦截器的核心功能**:Struts2拦截器可以动态地拦截发送到指定Action的请求,并在Action执行前后插入自定义的逻辑处理。 - **代码重用原则**:通过拦截器机制,可以将多个Action中需要重复指定的代码提取出来,...
Struts2拦截器.ppt Struts2拦截器.ppt Struts2拦截器.ppt
Struts2的拦截器基于AOP(面向切面编程)思想,允许开发者在不修改原有代码的情况下添加额外的功能。 在Struts2中,拦截器是基于Java的动态代理机制实现的。当你定义一个拦截器时,实际上创建了一个实现了`...
标题“struts2拦截器”指的是Struts2框架中的拦截器机制,这是一个关键的组件,可以让我们在不修改实际业务代码的情况下,实现对请求处理流程的扩展和定制。 描述中提到的“基于struts2的拦截器测试,实现了页面的...
在本场景中,我们将探讨如何使用Struts2拦截器来实现对不文明字迹或者敏感词汇的拦截。 首先,我们需要了解Struts2拦截器的工作原理。拦截器是基于AOP(面向切面编程)思想实现的,它通过在Action调用前后插入额外...
在Struts2中,拦截器是实现业务逻辑控制和增强功能的重要机制,它们扮演着类似于AOP(面向切面编程)的角色,允许在动作执行前后插入自定义逻辑。在这个“Struts2拦截器实现权限控制demo”中,我们将深入探讨如何...
拦截器是Struts2框架的核心组成部分,它们在请求处理流程中起到了关键的作用。在本文中,我们将深入探讨Struts2拦截器的工作原理。 ### 一、拦截器概念 拦截器是基于Java的动态代理机制实现的,它允许我们在Action...
7. **异常处理**:学习如何在Struts2中处理异常,如使用全局异常拦截器。 8. **国际化和本地化**:如果案例涉及,了解如何在Struts2中实现多语言支持。 通过深入学习和实践这个案例,你可以更全面地理解和掌握...
### Struts2拦截器的使用方法 #### 一、Struts2拦截器概述 ...通过以上介绍,我们可以了解到Struts2拦截器的基本使用方法及配置方式,这对于理解Struts2框架的工作机制以及优化应用架构具有重要意义。
通过这个例子,你可以学习到如何创建、注册拦截器,以及如何在Struts2框架中构建拦截器栈,从而更灵活地管理应用程序的行为。 总结来说,Struts2的拦截器机制提供了强大的功能,允许开发者以声明式的方式控制请求的...
在Struts2框架中,拦截器扮演着至关重要的角色,它们是实现业务逻辑、验证、日志记录等功能的核心组件。下面将详细探讨Struts2拦截器的源码及其工作原理。 首先,理解拦截器的定义:拦截器是AOP(面向切面编程)的...
当一个请求被发送到Struts2框架时,拦截器会按照配置的顺序依次对请求进行处理。每个拦截器都有`intercept()`方法,这个方法会在动作执行前和执行后被调用,允许开发者插入预处理和后处理逻辑。 **2. 拦截器的工作...
在Struts2配置中,我们需要将这个拦截器添加到拦截器栈中,通常是在`struts.xml`文件中。这样,每次请求到达Action之前,都会先经过这个拦截器: ```xml <struts> ...
Struts2拦截器是在访问某个Action或Action的某个方法,字段之前或之后实施拦截,并且Struts2拦截器是可插拔的,拦截器是AOP的一种实现.
默认情况下,Struts2提供了一些预定义的拦截器,如`params`(处理请求参数)、`exception`(处理异常)、`i18n`(国际化支持)等。这些拦截器的执行顺序对整个应用的性能和功能有直接影响。 当一个请求到达Struts2...