Struts2入门---增删改查- 拦截器栈的应用

Struts2 应用拦截器 ModelDriven 和 Prepare 实现对数据库的增删改查功能.....

先看看效果图:

  

jsp 静态代码我就不写了 ,以下是主要实现功能的java代码:

package com.struts.app;

import java.util.Map;

import org.apache.struts2.interceptor.RequestAware;

import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
public class EmployeeAction implements RequestAware, ModelDriven<Employee>, Preparable{
  
    private Dao dao = new Dao();  //实例化数据操作对象
    
    private Employee employee;    
    public String update(){       //定义update()方法
        dao.update(employee);     
        return "success";
    }
    public void prepareUpdate(){  //定义 prepare 拦截器需要的方法(作用:需要用到数据对象的方法可以单一的实现)
        employee = new Employee();
    }
    public String edit(){
        return "edit";
    }
    public void prepareEdit(){
        employee = dao.get(employeeId);
    }
    public String save(){
        dao.save(employee);
        return "success";
    }
    public void prepareSave(){
        employee = new Employee();
    }
    public String delete(){
        dao.delete(employeeId);
        //返回结果的类型应为: redirectAction.
        //也可以是 chain: 实际上 chain 是没有必要的 . 因为不需要在下一个 Action 中保留当前 Action 的状态
        //还有, 若使用 chain , 则达到目标页面后, 地址栏显示的 依然是删除时的那个连接. 刷新时会重复提交.. 
        return "success";
    }
    
    public String list(){
        request.put("emps", dao.getEmployees());
        return "list";
    }

    private Map<String, Object> request;
    @Override
    public void setRequest(Map<String, Object> request) {
        this.request = request;
        
    }
    private Integer employeeId;
    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }
    @Override
    public Employee getModel() {    //这是实现 ModelDriven 重写的 getModel()方法.作用是:获取model(这样避免了在Action中定义model)
        return employee;
    }
    /*
     *  prepare() 方法的主要作用: 为getModel() 方法 准备model 的.(避免了在执行方法时导致加载浪费(有些方法不需要加载对象 但它也加载了 浪费资源))
     */
    @Override
    public void prepare() throws Exception {
        System.out.println("prepare...");
    }
}

那为什么要这么写呢?这需要了解 Struts2 拦截器栈的运行流程

 

一般情况下我们默认的是 defaultStack 这个拦截器栈 由上图看出里面定义了许多的拦截器. 而struts 在运行的时候,都会进入这些拦截器一遍.以上每个拦截器有他对应的作用,在这里我就不一一举例了.

上面的代码就是根据 prepare拦截器 与 modelDriven拦截器 的特性所写:

  下面我们来看看 modelDriven 拦截器是怎么运行的:

    1.  Action 类需要实现 ModelDriven接口 并重写getModel() 方法

    2. ModelDriven 拦截器的运行流程: 

      1). 先会执行 ModelDrivenIntercepptor 的 intercept 方法.

 ------------------------------------源代码分析-----------------------------------------------
      public String intercept(ActionInvocation invocation) throws Exception {
      //获取 Action 对象: EmployeeAction 对象, 此时该 Action 已经实现了 ModelDriven 接口.
      Object action = invocation.getAction();

      //判断 action 是否 是 ModelDriven 的实例
      if (action instanceof ModelDriven) {
        //强制转换为 ModelDriven 类型
          ModelDriven modelDriven = (ModelDriven) action;
        //获取值栈
        ValueStack stack = invocation.getStack();
        //调用 ModelDriven 接口的 getModel()方法
        Object model = modelDriven.getModel();
        if (model != null) {
          //把getModel()的返回值 压入到值栈的栈顶.
          stack.push(model);
        }
        if (refreshModelBeforeResult) {
          invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
        }
      }
      return invocation.invoke();
      }

      2). 执行 ParametersInterceptor 的 intercept 方法:把请求参数的值赋给栈顶对象对应的属性 , 若栈顶对象没有对应的属性,
        则查询值栈中下一个对象对应的属性.   

      3). 注意: Struts2.5版本后 通配符的写法: Action 中的方法需要被允许.
        <action name="emp-*"
        class="com.struts.app.EmployeeAction"
        method="{1}">
          <result name="{1}">/emp-{1}.jsp</result>
          <result name="success" type="redirectAction">emp-list</result>
          <allowed-methods>list,save,edit,update,delete</allowed-methods>
        </action>

  同理 (prepare) PrepareInterceptor拦截器运行流程:

---------------------------------源代码解析--------------------------------------

public String doIntercept(ActionInvocation invocation) throws Exception {
    //获取 Action 实例
    Object action = invocation.getAction();
    
    //判断 Action 是否实现了 Preparable 接口
    if (action instanceof Preparable) {
        try {
            String[] prefixes;
            //根据当前拦截器的 firstCallPrepareDo(默认为false) 属性确定 prefixes
            if (firstCallPrepareDo) {
                prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
            } else {
                prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
            }
              // 若为 false,则 prefixes: prepare, prepareDo
              //调用前缀方法
            PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
        }
        catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            if (cause instanceof Exception) {
                throw (Exception) cause;
            } else if(cause instanceof Error) {
                throw (Error) cause;
            } else {
                throw e;
            }
        }
        //根据当前的 alwaysInvokePrepare(默认true) 决定是否 调用 Action 的 prepare 方法
        if (alwaysInvokePrepare) {
            ((Preparable) action).prepare();
        }
    }

    return invocation.invoke();
}

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(); //如果方法名为空 默认为 execute 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); if (method != null) { method.invoke(action, new Object[0]); } } PrefixMethodInvocationUtil.getPrefixedMethod 方法: public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) { assert(prefixes != null); //将方法名首字母置为大写(Update) 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 LOG.debug("Cannot find method [{}] in action [{}]", prefixedMethodName, action); } } return null; }

    解析源代码得出结论:

      若 Action 实现了 Preparable 接口 则 Struts2 将尝试执行 prepare[ActionMethodName] 方法,
      若 prepare[ActionMethodName] 不存在,则将尝试执行 prepareDo[ActionMethodName] 方法.
      若都不存在,就都不执行.

      若 PrepareInterceptor 的 alwaysInvokePrepare 属性为 false,
      则Struts2 将不会调用该实现了 Preparable 的 Action 的 prepare() 方法.

      可以为每一个 ActionMethod 准备 prepare[ActionMethodName] 方法, 而抛弃掉原来的 prepare() 方法
      将 PrepareInterceptor 的 alwaysInvokePrepare 属性设置为 false, 避免 Struts2 框架再调用 prepare() 方法.

      如何在配置文件中为拦截器栈的属性赋值:参看 /struts-2.5.12/docs/docs/interceptors.html

     <interceptors>
            <interceptor-stack name="myStack">
                <interceptor-ref name="paramsPrepareParamsStack">
                    <param name="prepare.alwaysInvokePrepare">false</param>
                </interceptor-ref>
            </interceptor-stack>
         </interceptors>
        <default-interceptor-ref name="myStack"/>

这个项目中我们需要用struts 的  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 的那个属性值决定 压入到值栈栈顶的对象, 最后再为栈顶对象的属性赋值.(用于表单发送请求参数不同给出不同的响应结果)

4). 关于回显: Struts2 表单标签会从值栈中获取对应的属性值进行回显.

以上就是关于struts 增删改查 与拦截器的简单应用..

posted @ 2017-08-02 23:59  cx_davis  阅读(328)  评论(0编辑  收藏  举报