第四章 使用拦截器追加工作流
4.1 为什么要拦截请求
在尽量隔离WEB应用的程序关注点时,拦截器极大地提高了分离水平。特别是,拦截器消除了Action组件中的横切任务。
4.2 拦截器的工作原理
框架不直接调用Action的execute()方法,而是创建一个叫做ActionInvocation的对象,它封装了Action执行相关的所有处理细节。
public interface ActionInvocation extends Serializable {
/**
* Get the Action associated with this ActionInvocation.
*
* @return the Action
*/
Object getAction();
/**
* Gets whether this ActionInvocation has executed before.
* This will be set after the Action and the Result have executed.
*
* @return <tt>true</tt> if this ActionInvocation has executed before.
*/
boolean isExecuted();
/**
* Gets the ActionContext associated with this ActionInvocation. The ActionProxy is
* responsible for setting this ActionContext onto the ThreadLocal before invoking
* the ActionInvocation and resetting the old ActionContext afterwards.
*
* @return the ActionContext.
*/
ActionContext getInvocationContext();
/**
* Get the ActionProxy holding this ActionInvocation.
*
* @return the ActionProxy.
*/
ActionProxy getProxy();
/**
* If the ActionInvocation has been executed before and the Result is an instance of {@link ActionChainResult}, this method
* will walk down the chain of <code>ActionChainResult</code>s until it finds a non-chain result, which will be returned. If the
* ActionInvocation's result has not been executed before, the Result instance will be created and populated with
* the result params.
*
* @return the result.
* @throws Exception can be thrown.
*/
Result getResult() throws Exception;
/**
* Gets the result code returned from this ActionInvocation.
*
* @return the result code
*/
String getResultCode();
/**
* Sets the result code, possibly overriding the one returned by the
* action.
* <p/>
* The "intended" purpose of this method is to allow PreResultListeners to
* override the result code returned by the Action.
* <p/>
* If this method is used before the Action executes, the Action's returned
* result code will override what was set. However the Action could (if
* specifically coded to do so) inspect the ActionInvocation to see that
* someone "upstream" (e.g. an Interceptor) had suggested a value as the
* result, and it could therefore return the same value itself.
* <p/>
* If this method is called between the Action execution and the Result
* execution, then the value set here will override the result code the
* action had returned. Creating an Interceptor that implements
* {@link PreResultListener} will give you this oportunity.
* <p/>
* If this method is called after the Result has been executed, it will
* have the effect of raising an IllegalStateException.
*
* @param resultCode the result code.
* @throws IllegalStateException if called after the Result has been executed.
* @see #isExecuted()
*/
void setResultCode(String resultCode);
/**
* Gets the ValueStack associated with this ActionInvocation.
*
* @return the ValueStack
*/
ValueStack getStack();
/**
* Register a {@link PreResultListener} to be notified after the Action is executed and
* before the Result is executed.
* <p/>
* The ActionInvocation implementation must guarantee that listeners will be called in
* the order in which they are registered.
* <p/>
* Listener registration and execution does not need to be thread-safe.
*
* @param listener the listener to add.
*/
void addPreResultListener(PreResultListener listener);
/**
* Invokes the next step in processing this ActionInvocation.
* <p/>
* If there are more Interceptors, this will call the next one. If Interceptors choose not to short-circuit
* ActionInvocation processing and return their own return code, they will call invoke() to allow the next Interceptor
* to execute. If there are no more Interceptors to be applied, the Action is executed.
* If the {@link ActionProxy#getExecuteResult()} method returns <tt>true</tt>, the Result is also executed.
*
* @throws Exception can be thrown.
* @return the return code.
*/
String invoke() throws Exception;
/**
* Invokes only the Action (not Interceptors or Results).
* <p/>
* This is useful in rare situations where advanced usage with the interceptor/action/result workflow is
* being manipulated for certain functionality.
*
* @return the return code.
* @throws Exception can be thrown.
*/
String invokeActionOnly() throws Exception;
/**
* Sets the action event listener to respond to key action events.
*
* @param listener the listener.
*/
void setActionEventListener(ActionEventListener listener);
void init(ActionProxy proxy) ;
}
当框架收到一个请求时,它首先决定这个URL映射到哪个Action。这个Action的一个实例会被加入到一个新创建的ActionInvocation实例中。接着,框架咨询声明性架构(通过应用程序的XML或者Java注解),以发现哪些拦截器应该触发,以及按什么顺序触发。除了这些核心元素,ActionInvocation还拥有对其他重要信息(例如Servlet请求对象和当前Action可用的结果组件的映射)的引用。
ActionInvocation公开了invoke()方法,框架通过调用这个方法开始Action的执行。当框架调用这个方法时,ActionInvocation通过执行拦截器栈中第一个拦截器开始这个调用过程。注意,invoke()方法并不总是映射到第一个拦截器,ActionInvocation负责跟踪执行过程到达的状态,并且通过调用拦截器的intercept()方法把控制权交给栈中合适的拦截器。重要的是,intercept()方法把ActionInvocation实例作为一个参数。
public String intercept(ActionInvocation invocation) throws Exception {
...
}
后续拦截器的继续执行,最终执行Action,这些都是通过递归调用ActionInvocation的invoke()方法实现。每次invoke()方法被调用时,ActionInvocation都会查询自身的状态,调用接下来的任何一个拦截器。
因此,在通常的执行中,调用过程向下通过所有拦截器,直到栈中再也没有拦截器时,触发Action。另外,ActionInvocation在内部管理处理状态,因此它总是直到自己现在处在栈的什么位置。
拦截器在触发时能做什么:
- 做一些预处理。在这个阶段拦截器可以用来准备、过滤、改变或者操作任何可以访问的重要数据。这些数据包括所有与当前请求相关的关键对象和数据,也包括Action。
- 通过调用invoke()方法将控制转移给后续的拦截器,直到Action。或者通过返回一个控制字符串中断执行。在这个阶段,如果拦截器决定请求不应该继续,他可以不调用ActionInvocation实例上的invoke()方法,而是直接返回一个控制字符串。通过这种方式可以停止后续的执行,并且决定哪个结果被呈现。
- 做一些后加工。在这个阶段,任何一个返回的拦截器可以修改可以访问的对象的数据作为后加工,但是此时结果已经确定了。
4.3 研究内建的Struts 2拦截器
4.3.1 工具拦截器
- timer拦截器。记录执行花费的时间
- logger拦截器。提供了一个简单的日志记录机制
4.3.2 数据转移拦截器
- params拦截器(defaultStack)。params拦截器不知道这些数据最终会去哪里,它只是把数据转移到ValueStack上发现的第一个匹配的属性上。
- static-params拦截器(defaultStack)。它也将参数转移到ValueStack公开的属性上,不同的是参数的来源。这个拦截器转移的参数定义在声明性架构的Action元素中。
- autowiring拦截器。这个拦截器为使用Spring管理应用程序资源提供了一个集成点。
- servlet-config拦截器(defaultStack)。该拦截器提供了一种将来源于Servlet API的各种对象注入到Action的简洁方法。这个拦截器通过将各种对象设置到Action必须实现的接口公开的设置方法的方式工作。每个接口包含一个方法——当前资源的设置方法。不同的接口用于获取与Servlet环境相关的不同对象。
ServletContextAware设置ServletContext
ServletRequestAware设置HttpServletRequest
ServletResponseAware设置HttpServletResponse
ParameterAware设置Map类型的请求参数
RequestAware设置Map类型的请求属性
SessionAware设置Map类型的会话属性
ApplicationAware设置Map类型的应用程序领域属性
PrincipalAware设置Principal对象(安全相关)
- fileUpload拦截器(defaultStack)。该拦截器将文件和元数据从多重请求转换为常规的请求参数。
4.3.3 工作流拦截器
- workflow拦截器(defaultStack)
- validation拦截器(defaultStack),提供了声明性的方式验证你的数据。
- prepare拦截器(defaultStack),提供了一种向Action追加额外的工作流处理的通用入口点。
- modelDriven拦截器(defaultStack)
4.3.4 其他拦截器
- exception拦截器(defaultStack)。它出现在defaultStack中第一位,也应在在任何你创建的自定义拦截器栈中出现在第一位。当exception拦截器在后加工阶段处理时,它会捕获被抛出的任何异常,并且把结它映射到一个结果页面。在将控制转交给结果之前,exception拦截器会创建一个ExceptionHolder对象,并且把它放在ValueStack的最顶端。ExceptionHolder是一个异常的包装器,它把跟踪栈和异常作为JavaBean属性公开出来,可以在错误页面通过标签访问这些属性。
- token拦截器和token-session拦截器。可以作为避免表单重复提交系统的一部分。
- scoped-modelDriven拦截器(defaultStack)。这个拦截器为Action的模型对象提供跨请求的向导式的持久性。
- execAndWait拦截器。当一个请求需要执行很长时间时,最好能给用户一些反馈。
4.4 声明拦截器
此时XML是声明拦截器的唯一选择。注解机制现在还不支持声明拦截器。
只要Action声明了自己的拦截器,它就失去了自动的默认值。
4.5 构建自定义拦截器
拦截器实例在Action之间共享。虽然每个请求都会创建动作的一个新实例,但是拦截器会重用。拦截器是无状态的,不要在拦截器中存储与当前正在处理的请求相关的数据。