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     }
View Code

  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.  把栈顶对象的属性回显在表单中.

流程如下:

  –1. params拦截器首先给action中的相关参数赋值,如id
  –2. prepare拦截器执行prepare方法,prepare方法中会根据参数,如id,去调用业务逻辑,设置model对象
  –3. modelDriven拦截器将model对象压入value stack,这里的model对象就是在prepare中创建的
  –4. params拦截器再将参数赋值给model对象
  –5. action的业务逻辑执行

  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 }
View Code

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]);
    }
}
View Code

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;
}
View Code

 

转载于:https://www.cnblogs.com/linyueshan/p/5686623.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值