struts2 ModelDriven 和 Preparable 拦截器
Struts2 运行流程图-1
ActionProxy 是 Action 的一个代理类,也就是说Action的调用是通过 ActionProxy 实现的,其实就是调用了ActionProxy.execute()方法,而该方法又调用了ActionInvocation.invoke()方法.
ActionInvocation就是Action的调用者。ActionInvocation在Action的执行过程中,负责Interceptor、Action和Result等一系列元素的调度。
默认的拦截器栈
Params 拦截器
Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueStack 栈中的下一个对象
把 Action 和 Model 隔开
在使用 Struts 作为前端的企业级应用程序时把 Action 和 Model 清晰地隔离开是有必要的: 有些 Action 类不代表任何Model 对象, 它们的功能仅限于提供显示服务
如果 Action 类实现了 ModelDriven 接口,该拦截器将把 ModelDriven 接口的 getModel() 方法返回的对象置于栈顶
ModelDriven 拦截器
Action 实现 ModelDriven 接口后的运行流程
1). 先会执行 ModelDrivenInterceptor 的 intercept 方法.
1 public String intercept(ActionInvocation invocation) throws Exception { 2 //获取 Action 对象: EmployeeAction 对象, 此时该 Action 已经实现了 ModelDriven 接口 3 //public class EmployeeAction implements RequestAware, ModelDriven<Employee> 4 Object action = invocation.getAction(); 5 6 //判断 action 是否是 ModelDriven 的实例 7 if (action instanceof ModelDriven) { 8 //强制转换为 ModelDriven 类型 9 ModelDriven modelDriven = (ModelDriven) action; 10 //获取值栈 11 ValueStack stack = invocation.getStack(); 12 //调用 ModelDriven 接口的 getModel() 方法 13 //即调用 EmployeeAction 的 getModel() 方法 14 /* 15 public Employee getModel() { 16 employee = new Employee(); 17 return employee; 18 } 19 */ 20 Object model = modelDriven.getModel(); 21 if (model != null) { 22 //把 getModel() 方法的返回值压入到值栈的栈顶. 实际压入的是 EmployeeAction 的 employee 成员变量 23 stack.push(model); 24 } 25 if (refreshModelBeforeResult) { 26 invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); 27 } 28 } 29 return invocation.invoke(); 30 }
2). 执行 ParametersInterceptor 的 intercept 方法: 把请求参数的值赋给栈顶对象对应的属性. 若栈顶对象没有对应的属性, 则查询值栈中下一个对象对应的属性...
3). 注意: getModel 方法不能提供以下实现. 的确会返回一个 Employee 对象到值栈的栈顶. 但当前 Action 的 employee 成员变量却是 null.
public Employee getModel() { return new Employee(); }
Preparable 拦截器
Struts 2.0 中的 modelDriven 拦截器负责把 Action 类以外的一个对象压入到值栈栈顶, 而 prepare 拦截器负责准备为 getModel() 方法准备 model
PrepareInterceptor拦截器用方法
若 Action 实现 Preparable 接口,则 Action 方法需实现 prepare() 方法
PrepareInterceptor 拦截器将调用 prepare() 方法,prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法
PrepareInterceptor 拦截器根据 firstCallPrepareDo 属性决定获取 prepareActionMethodName 、prepareDoActionMethodName的顺序。默认情况下先获取 prepareActionMethodName (), 如果没有该方法,就寻找prepareDoActionMethodName()。如果找到对应的方法就调用该方法
PrepareInterceptor 拦截器会根据 alwaysInvokePrepare 属性决定是否执行prepare()方法
使用 paramsPrepareParamsStack 拦截器栈
Struts 2.0的设计上要求 modelDriven 在 params 之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在 prepare之前运行params拦截器设置相关参数,这个也就是创建paramsPrepareParamsStack的原因。
使用 paramsPrepareParamsStack 拦截器栈后的运行流程
1). paramsPrepareParamsStack 和 defaultStack 一样都是拦截器栈. 而 struts-default 包默认使用的是 defaultStack
2). 可以在 Struts 配置文件中通过以下方式修改使用的默认的拦截器栈
<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>
3). paramsPrepareParamsStack 拦截器在于 params -> modelDriven -> params
所以可以先把请求参数赋给 Action 对应的属性, 再根据赋给 Action 的那个属性值决定压到值栈栈顶的对象, 最后再为栈顶对象的属性赋值.
对于 edit 操作而言:
I. 先为 EmployeeAction 的 employeeId 赋值
II. 根据 employeeId 从数据库中加载对应的对象, 并放入到值栈的栈顶
III. 再为栈顶对象的 employeeId 赋值(实际上此时 employeeId 属性值已经存在)
IV. 把栈顶对象的属性回显在表单中.
流程如下:
4). 关于回显: Struts2 表单标签会从值栈中获取对应的属性值进行回显.
使用 PrepareInterceptor 和 Preparable 接口.
关于 PrepareInterceptor [分析后得到的结论]
若 Action 实现了 Preparable 接口, 则 Struts 将尝试执行 prepare[ActionMethodName] 方法,
若 prepare[ActionMethodName] 不存在, 则将尝试执行 prepareDo[ActionMethodName] 方法. 若都不存在, 就都不执行.
若 PrepareInterceptor 的 alwaysInvokePrepare 属性为 false, 则 Struts2 将不会调用实现了 Preparable 接口的 Action 的 prepare() 方法
可以为每一个 ActionMethod 准备 prepare[ActionMethdName] 方法, 而抛弃掉原来的 prepare() 方法, 将 PrepareInterceptor 的 alwaysInvokePrepare 属性置为 false, 以避免 Struts2 框架再调用 prepare() 方法.
如何在配置文件中为拦截器栈的属性赋值: 参看 /struts-2.3.15.3/docs/WW/docs/interceptors.html.
例子:
<!-- 配置使用 paramsPrepareParamsStack 作为默认的拦截器栈 --> <!-- 修改 PrepareInterceptor 拦截器的 alwaysInvokePrepare 属性值为 false --> <interceptors> <interceptor-stack name="atguigustack"> <interceptor-ref name="paramsPrepareParamsStack"> <param name="prepare.alwaysInvokePrepare">false</param> </interceptor-ref> </interceptor-stack> </interceptors> <default-interceptor-ref name="atguigustack"/>
源代码解析
1 public String doIntercept(ActionInvocation invocation) throws Exception { 2 //获取 Action 实例 3 Object action = invocation.getAction(); 4 5 //判断 Action 是否实现了 Preparable 接口 6 if (action instanceof Preparable) { 7 try { 8 String[] prefixes; 9 //根据当前拦截器的 firstCallPrepareDo(默认为 false) 属性确定 prefixes 10 if (firstCallPrepareDo) { 11 prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX}; 12 } else { 13 prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX}; 14 } 15 //若为 false, 则 prefixes: prepare, prepareDo 16 //调用前缀方法. 17 PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes); 18 } 19 catch (InvocationTargetException e) { 20 21 Throwable cause = e.getCause(); 22 if (cause instanceof Exception) { 23 throw (Exception) cause; 24 } else if(cause instanceof Error) { 25 throw (Error) cause; 26 } else { 27 throw e; 28 } 29 } 30 31 //根据当前拦截器的 alwaysInvokePrepare(默认是 true) 决定是否调用 Action 的 prepare 方法 32 if (alwaysInvokePrepare) { 33 ((Preparable) action).prepare(); 34 } 35 } 36 37 return invocation.invoke(); 38 }
PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes) 方法:
public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException { //获取 Action 实例 Object action = actionInvocation.getAction(); //获取要调用的 Action 方法的名字(update) String methodName = actionInvocation.getProxy().getMethod(); if (methodName == null) { // if null returns (possible according to the docs), use the default execute methodName = DEFAULT_INVOCATION_METHODNAME; } //获取前缀方法 Method method = getPrefixedMethod(prefixes, methodName, action); //若方法不为 null, 则通过反射调用前缀方法 if (method != null) { method.invoke(action, new Object[0]); } }
PrefixMethodInvocationUtil.getPrefixedMethod 方法:
public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) { assert(prefixes != null); //把方法的首字母变为大写 String capitalizedMethodName = capitalizeMethodName(methodName); //遍历前缀数组 for (String prefixe : prefixes) { //通过拼接的方式, 得到前缀方法名: 第一次 prepareUpdate, 第二次 prepareDoUpdate String prefixedMethodName = prefixe + capitalizedMethodName; try { //利用反射获从 action 中获取对应的方法, 若有直接返回. 并结束循环. return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY); } catch (NoSuchMethodException e) { // hmm -- OK, try next prefix if (LOG.isDebugEnabled()) { LOG.debug("cannot find method [#0] in action [#1]", prefixedMethodName, action.toString()); } } } return null; }