struts2 ModelDriven & Prepareable 拦截器

前面对于 Struts2 的开发环境的搭建、配置文件以及 Struts2 的值栈都已经进行过叙述了!这次博文我们讲解利用 Struts2 进行 CURD 操作,体验和 Servlet 的不一样,以此案例理解 ModelDrivenPrepareable 拦截器!

案例流程

  • 获取数据库数据并将之显示在 employee-show.jsp 页面上
  • employee-show.jsp 页面可以添加员工信息到数据库,对现有的员工信息进行编辑以及删除操作
  • 在将删除或编辑请求传到 action 方法时且将要操作的员工的 empId 以参数的形式传入
  • 对现有员工信息进行编辑的时候需要先将其信息回显到表单再进行编辑

解决思路

Employee-show.jsp

  • 首先将所有员工信息获取到并于页面展示,如下 JSP 页面发送请求经由 Action 类处理将所有的员工信息传回页面,在页面进行显示(为了方便我们将所有数据存入一个 Map 对象中,使用 Dao 类进行处理
  • emp-show.jsp 页面中利用<s:iterator></s:iterator> 标签处理从 action 方法传回的员工信息的 List,并显示。
  • 如上显示页面中每个员工信息行尾都会添加两个操作的超链接分别为 Edit 和 Delete

Employee-edit.jsp

  • 点击某员工行后的 edit 超链接,其流程如上显示页面所述。struts.xml 文件中使用的是通配符映射,所以其经过 Action 方法处理将会到达 Employee-edit.jsp 页面。
  • 点击edit超链接的时候会将所操作的员工的 id 传入 action 方法,即 edit() 方法,edit() 方法将会从现有的员工信息中获得对应的员工的信息将其回显在 Employee-edit.jsp 的表单上
  • 在回显的页面上可以进行修改员工信息,点击提交执行 update() 方法,将更新存入 Map,并跳转到显示页面,实时显示更改的员工信息

emp-delete.action

  • 点击员工行后的 delete 超链接,由于删除不需要任何页面,所以执行完删除操作之后跳转到 emp-show.action 显示操作后的员工信息。
  • 点击 delete 超链接的时候会将所操作的员工的 Id 传入 action 方法,即 delete() 方法,delete() 方法从员工信息库中删除对应的员工信息然后将重定向到 emp-show.action,显示删除后的员工信息

emp-add.action

  • 在 employee-show.jsp 页面的添加表单上填写将要添加的员工信息点击提交后执行 emp-show.action 获取新的员工信息列表并显示
  • 点击 submit 后,将员工信息保存到一个新的对象中,执行 add() 方法将新的对象添加到存放用户列表中,再重定向到 emp-show.action,显示新的员工信息

案例目录

  • 如上目录,其代码如下

      public class Dao {
    
          private static Map<String, Employoee> empMap = new LinkedHashMap<String, Employoee>();
    //初始化所有的员工信息
    static {
    empMap.put("1001", new Employoee(1001, "ZZ", "XY", "110"));
    empMap.put("1002", new Employoee(1002, "YS", "JJ", "120"));
    empMap.put("1003", new Employoee(1003, "JC", "HJ", "119"));
    empMap.put("1004", new Employoee(1004, "KF", "LT", "10086"));
    empMap.put("1005", new Employoee(1005, "DX", "ZG", "10000"));
    }
    //将所有的员工信息存入 List 以便返回页面
    public List<Employoee> getEmployee() {
    return new ArrayList<Employoee>(empMap.values());
    }
    //根据 empId 获得某一个员工的信息
    public Employoee getEmployee(String empId) {
    return empMap.get(empId);
    }
    // 根据 empId 从 Map 集合中删除某一个员工
    public void deleteEmp(String empId) {
    empMap.remove(empId);
    }
    // 根据传入的 Employee 对象将其添加到 Map 集合之中
    public void addEmp(Employoee employoee) {
    long sysTime = System.currentTimeMillis();
    employoee.setEmpId((int) sysTime);
    System.out.println(employoee.getEmpId());
    empMap.put(String.valueOf(employoee.getEmpId()), employoee);
    }
    // 根据传入的 Employee 对象传入更新现有的 Employee 对象
    public void updateEmp(Employoee employoee) {
    empMap.put(String.valueOf(employoee.getEmpId()), employoee);
    }
    }
  • Employee.java

      public class Employoee {
    
          private Integer empId;
    private String empName;
    private String empAddress;
    private String empNum; @Override
    public String toString() {
    return "Employoee{" +
    "empId=" + empId +
    ", empName='" + empName + '\'' +
    ", empAddress='" + empAddress + '\'' +
    ", empNum='" + empNum + '\'' +
    '}';
    } public Employoee(Integer empId, String empName, String empAddress, String empNum) {
    this.empId = empId;
    this.empName = empName;
    this.empAddress = empAddress;
    this.empNum = empNum;
    } public Employoee() { } //getXxx()、setXxx() 方法此处省略
    }
  • EmployeeCurd.java

      public class EmployeeCurd implements RequestAware {
    private Dao dao = new Dao();
    private Map<String, Object> requestMap;
    private Integer empId;
    private String empName;
    private String empAddress;
    private String empNumber;
    private Employee employee; /*getXxx()、setXxx() 方法此处省略*/ public String delete() {
    dao.deleteEmp(String.valueOf(empId));
    return "delete";
    } public String add() {
    // 将表单的字段值为该类的属性赋值,即可初始化 Employee 对象
    Employee employoee = new Employoee(null, empName, empAddress, empNumber);
    // 调用 Dao 方法将该对象加入
    dao.addEmp(employoee);
    return "add";
    } public String show() {
    // 调用 Dao 的方法获得所有员工信息
    List<Employoee> employees = dao.getEmployee();
    // 将所有员工信息存入 request 域,并在页面进行显示
    requestMap.put("empList", employees);
    return "show";
    }
    // 获得 WEB 资源 request
    @Override
    public void setRequest(Map<String, Object> map) {
    this.requestMap = map;
    }
    }
  • Employee-edit.jsp

      <s:form action="emp-update">
    <s:hidden name="empId"></s:hidden>
    <s:textfield label="EmpName" name="empName"></s:textfield>
    <s:textfield label="EmpAddress" name="empAddress"></s:textfield>
    <s:textfield label="EmpNumber" name="empNum"></s:textfield>
    <s:submit value="Submit"></s:submit>
    </s:form>

思考

  • 如上代码,我们在EmployeeCurd.java 中以 show()、add()、delete() 方法为例,其中在 add() 方法时所使用的 Employee 对象以该类的属性初始化,那么该类的属性是如何被初始化的?执行 delete 方法时传入的 empId 又是如何给对应的属性赋值?

    • 这些操作看似我们没有对其进行任何处理,但实际上 struts2 的 params 拦截器为我们将这些都做了,params 拦截器的功能是将表单属性值为栈顶对象的对应的属性赋值,即 add() 方法执行前将表单中对应的字段值赋值给栈顶对象(栈顶对象默认为 Action 类的对象,即 EmployeeCurd 对象)。
    • 删除操作时执行 delete() 方法会根据 empId 而去操作对象,这就使得我们需要在执行 delete() 方法前获取到传入的 empId,我们知道 params 拦截器会根据栈顶属性去赋值,但是在默认拦截器栈中 paramsModelDriven 拦截器其后,这时就需要使用 paramsPrepareParamsStack 拦截器栈,相比于默认拦截器此拦截器会在 ModelDriven 拦截器执行前先去执行一次 params 拦截器,在其后再执行一次 params 拦截器,这样的话 getModel 拦截器就会用到传入的 empId 参数,而我们也可以利用 empId 是否为空压入栈顶对应的对象,即添加操作时需要一个空的对象,更新操作时需要根据 empId 获取到已有对象压入栈顶以便回显
  • 在 struts.xml 文件中配置使用 paramsPrepareParamsStack 拦截器栈,如下配置需要在 标签中

      <default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>

回显问题

  • 对现有员工信息进行编辑的时候在编辑表单会进行回显,是因为 struts2 的表单标签会自动在值栈中寻找匹配的属性值进行回显,例如执行了 emp-edit.action 执行的后 getModel() 方法会根据 empId 将从 Map 集合中获取到的对象压入栈顶,那么在显示页面的时候会从栈顶获取对应的对象为 struts2 的表单标签赋值

缺点

  • EmployeeCurdEmployee 类中属性存在冗余,那么我们该如何解决?Struts2 默认的拦截器栈为我们提供了 ModelDriven 拦截器以解决此问题
  • 实现
    • Action 类实现 ModelDriven 接口
    • 使用 ModelDriven 拦截器的优点
      • Action 类和 Model 类将不存在冗余,Action 类更加简洁

实现 ModelDriven 接口的 Action 类

public class EmployeeCurd implements RequestAware, ModelDriven<Employoee> {
private Dao dao = new Dao();
private Map<String, Object> requestMap;
private Employoee employoee;
private Integer empId; public Integer getEmpId() {
return empId;
} public void setEmpId(Integer empId) {
this.empId = empId;
} public String delete() {
dao.deleteEmp(String.valueOf(empId));
return "delete";
} public String update() {
dao.updateEmp(employoee);
return "update";
} public String add() {
dao.addEmp(employoee);
return "add";
} public String show() {
List<Employoee> employees = dao.getEmployee();
requestMap.put("empList", employees);
return "show";
} public String edit() {
return "edit";
} @Override
public void setRequest(Map<String, Object> map) {
this.requestMap = map;
} @Override
public Employoee getModel() {
/*
* 判断调用该拦截器的时候是 edit 还是 show,这取决于是否有参数 empId,决定了 Employee 对象是新建还是依据 empId 获取
* */
if (empId != null) {
employoee = dao.getEmployee(String.valueOf(empId));
} else {
employoee = new Employoee();
}
return employoee;
}

  • 详解

    • ModelDriven 拦截器使用 getModel() 方法将对应的对象压入栈顶,例如 add() 方法执行的时候 getModel() 方法执行后其栈顶为 employee 对象,这样便可以利用 params 拦截器将表单对应的字段属性值赋给栈顶对象对应的属性值
  • 源码解析(ModelDriven 拦截器的 intercept 方法)

      public String intercept(ActionInvocation invocation) throws Exception {
    //获取 Action 对象: EmployeeCurd 对象, 此时该 Action 已经实现了 ModelDriven 接口
    //public class EmployeeAction implements RequestAware, ModelDriven<Employee>
    Object action = invocation.getAction(); //判断 action 是否是 ModelDriven 的实例
    if (action instanceof ModelDriven) {
    //强制转换为 ModelDriven 类型
    ModelDriven modelDriven = (ModelDriven) action;
    //获取值栈
    ValueStack stack = invocation.getStack();
    //调用 ModelDriven 接口的 getModel() 方法
    //即调用 EmployeeAction 的 getModel() 方法
    /*
    public Employee getModel() {
    employee = new Employee();
    return employee;
    }
    */
    Object model = modelDriven.getModel();
    if (model != null) {
    //把 getModel() 方法的返回值压入到值栈的栈顶. 实际压入的是 EmployeeCurd 的 employee 成员变量
    stack.push(model);
    }
    if (refreshModelBeforeResult) {
    invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
    }
    }
    return invocation.invoke();
    }

CURD 操作进阶之 PrepareInterceptor 拦截器

  • 以上实现存在的问题:

    • 在进行删除操作的时候会传入 empId,而 getModel() 方法判断到 empId 不为空,会从 Map 集合中去获取一个 Employee 对象置于栈顶,而对于 delete 操作不需要对象
    • 在显示所有员工的时候 getModel() 方法会创建一个空的 Employee 对象置于栈顶,而对于此操作也是没有必要的
  • 解决方案 - 使用 PrepareInterceptor 拦截器

  • 实现

    • Action 类实现 Preparable 接口
  • 查看源码

      public String doIntercept(ActionInvocation invocation) throws Exception {
    // 获取 Action 对象
    Object action = invocation.getAction();
    // 判断其是否实现了 Prepareable 拦截器
    if(action instanceof Preparable) {
    try {
    // 存取前缀,不是 prepare 就是 prepareDo
    String[] prefixes;
    // 判断 firstCallPrepareDo 属性,其默认为 false
    if(this.firstCallPrepareDo) {
    // 若 firstCallPrepareDo为 true, prefixes 的值为 prepareDoXxx,prepareXxx
    prefixes = new String[]{"prepareDo", "prepare"};
    } else {
    // 若 firstCallPrepareDo为 false,prefixes 的值为 prepareXxx ,prepareDoXxx
    prefixes = new String[]{"prepare", "prepareDo"};
    }
    // 执行 invokePrefixMethod() 方法,方法名为 prefixes 数组的值
    PrefixMethodInvocationUtil.invokePrefixMethod(invocation, prefixes);
    } catch (InvocationTargetException var5) {
    Throwable cause = var5.getCause();
    if(cause instanceof Exception) {
    throw (Exception)cause;
    }
    if(cause instanceof Error) {
    throw (Error)cause;
    }
    throw var5;
    }
    // 判断 alwaysInvokePrepare 属性的值,其默认为 true。
    // 若其值为 true 则每次都会调用 Action 的 prepare() 方法,
    if(this.alwaysInvokePrepare) {
    ((Preparable)action).prepare();
    }
    } return invocation.invoke();
    } public static void invokePrefixMethod(ActionInvocation actionInvocation, String[] prefixes) throws InvocationTargetException, IllegalAccessException {
    // 获取 Action 实例
    Object action = actionInvocation.getAction();
    // 获取要调用的 Action 的方法的名字
    String methodName = actionInvocation.getProxy().getMethod();
    if(methodName == null) {
    methodName = "execute";
    }
    // 获取前缀方法
    Method method = getPrefixedMethod(prefixes, methodName, action);
    if(method != null) {
    // 若方法不为空则通过反射调用该前缀方法
    method.invoke(action, new Object[0]);
    } }
    // 获取前缀方法
    public static Method getPrefixedMethod(String[] prefixes, String methodName, Object action) {
    assert prefixes != null;
    // 把方法的首字母变为大写
    String capitalizedMethodName = capitalizeMethodName(methodName);
    String[] arr$ = prefixes;
    int len$ = prefixes.length;
    int i$ = 0;
    // 遍历前缀数组
    while(i$ < len$) {
    String prefixe = arr$[i$];
    // 通过拼接的方式得到前缀加方法名,第一次为 prepareUpdate 第二次为 prepareDoUpdate
    String prefixedMethodName = prefixe + capitalizedMethodName;
    try {
    // 通过反射从 action 对象中调用方法,并返回
    return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
    } catch (NoSuchMethodException var10) {
    if(LOG.isDebugEnabled()) {
    LOG.debug("cannot find method [#0] in action [#1]", new String[]{prefixedMethodName, action.toString()});
    }
    ++i$;
    }
    }
    return null;
    }
  • 结论

    • 阅读源码可以得知若 Action 实现了 Preparable 接口, 则 Struts 将尝试执行 prepare[ActionMethodName] 方法, 若 prepare[ActionMethodName] 不存在, 则将尝试执行 prepareDo[ActionMethodName] 方法.若都不存在, 就都不执行
    • 若 PrepareInterceptor 的 alwaysInvokePrepare 属性为 false, 则 Struts2 将不会调用实现了 Preparable 接口的 Action 的 prepare() 方法,即 prepare() 可以不去实现而为每一个 Action 方法准备一个 prepareXxx 或 prepareDoXxx 方法,然后将 alwaysInvokePrepare 属性设为 false,那么每次执行就不会触发 prepare 方法
    • 若实现了此接口,那么每个 prepareXxx 方法就会为对应的 Xxx 方法准备一个 Model,利用 getModel() 方法将其置于栈顶,而不需要 empId 去判断,影响程序效率。说白了就是 prepareXxx 方法是为 getModel 方法准备返回对象的
  • 最终代码(EmployeeCurd.java)

      public class EmployeeCurd implements RequestAware, ModelDriven<Employoee>, Preparable {
    private Dao dao = new Dao();
    private Map<String, Object> requestMap;
    private Employoee employoee;
    private Integer empId; public Integer getEmpId() {
    return empId;
    } public void setEmpId(Integer empId) {
    this.empId = empId;
    } public String delete() {
    dao.deleteEmp(String.valueOf(empId));
    return "delete";
    } /*
    * 更新和添加一样,需要准备一个新的 employee 对象
    * */
    public String update() {
    dao.updateEmp(employoee);
    return "update";
    } /*
    * 准备一个新的 Employee 对象
    * */
    public void prepareUpdate() {
    employoee = new Employoee();
    } /*
    *
    * 添加到栈顶,使用 ModelDriven 拦截器和 paramsPrepareParmas 拦截器栈之后我们利用 ModelDriven 拦截器将 employee 对象添加到
    * 栈顶,不需要为 Action 类创建对应的属性,利用 ModelDriven 将对应的对象添加到栈顶之后执行 params 拦截器时便将请求参数和栈顶
    * 对象对应的属性赋值,使用了 prepare 拦截器之后我们在执行 ModelDriven 拦截器之前利用 prepare 拦截器准备好 model 不需要在
    * ModelDriven 拦截器中创建对应的对象
    * */
    public String add() {
    dao.addEmp(employoee);
    return "add";
    } /*
    * 利用 ModelDriven 和 prepare 拦截器将对应的 model 添加到栈顶之后并利用 params 拦截器为其赋值,填充栈顶对象,执行完所有的
    * 拦截器之后执行 add() 方法,此时的 employee 对象便为已经填充的对象
    * */
    public void prepareAdd() {
    employoee = new Employoee();
    } /*
    * 将已有的数据显示的时候不需要准备 model,所以不需要准备 prepareXxx 方法
    * */
    public String show() {
    List<Employoee> employees = dao.getEmployee();
    requestMap.put("empList", employees);
    return "show";
    } public String edit() {
    return "edit";
    } /*
    * 对现有的内容做出修改的时候需要进行回显,所以需要使用 prepare 拦截器为 ModelDriven 拦截器准备 model,这样的话便可
    * 利用现有的对象实现回显(回显就是利用与栈顶对象匹配的元素去回显)
    * */
    public void prepareEdit() {
    employoee = dao.getEmployee(String.valueOf(empId));
    } @Override
    public void setRequest(Map<String, Object> map) {
    this.requestMap = map;
    } /*
    * 实现 ModelDriven 拦截器,getModel 方法将把返回对象置于栈顶
    * */
    @Override
    public Employoee getModel() {
    return employoee;
    } /*
    * 若不设置 PrepareInterceptor 拦截器的 alwaysInvokePrepare 属性为 false,那么每次调用 action 方法都会执行 prepare 方法
    * 若设置其为 false,那么每次调用 Action 方法的时候就不会去调用 prepare 方法
    * 我们可以为某些 Action 方法实现 prepareXxx 方法,其为私有定制的方法等同于 prepare 方法,其功能和 prepare 方法等效,都是
    * 为 modelDriven 拦截器准备 model,然后利用 modelDriven 将 model 放置在栈顶,这样的话 getModel 和 prepare 方法就不需要
    * 去判断是新建对象还是从现有的中获取。
    * 在 ModelDriven 拦截器之前执行 params 拦截器时是为栈顶对象 Action 类对应的属性赋值,该例中 Action 类的属性只有 empId
    * */
    @Override
    public void prepare() throws Exception {
    System.out.println("prepare");
    }
    }
  • 此时的 Action 类的 action 方法就非常的简洁,不会在有其他的冗余问题。

  • 在 struts.xml 文件中配置 alwaysInvokePrepare 属性为 false,如下:

      <interceptors>
    <interceptor-stack name="myInterceptor">
    <interceptor-ref name="paramsPrepareParamsStack">
    <param name="prepare.alwaysInvokePrepare">false</param>
    </interceptor-ref>
    </interceptor-stack>
    </interceptors> <!--配置默认的拦截器为我们更改后的拦截器栈,同时也需要在 package 标签内部-->
    <default-interceptor-ref name="myInterceptor"/>

至此就是有关 ModelDriven 和 prepare 拦截器的案例,中间存在任何问题和表述不足的还望大神指出,谢谢!

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2tk2b3bnabmsw

Struts2 之 modelDriven & prepare 拦截器详解的更多相关文章

  1. Struts2中的拦截器详解

    exception:异常拦截器,拦截异常aliasservletConfig18nprepare:预备拦截器,这个拦截器就是为了ModelDriven准备对象的,若Action类实现了preparab ...

  2. struts2 18拦截器详解(七)

    ChainingInterceptor 该拦截器处于defaultStack第六的位置,其主要功能是复制值栈(ValueStack)中的所有对象的所有属性到当前正在执行的Action中,如果说Valu ...

  3. struts2 18拦截器详解(十)

    ModelDrivenInterceptor 该拦截器处于defaultStack中的第九的位置,在ScopedModelDrivenInterceptor拦截器之后,要使该拦截器有效的话,Actio ...

  4. struts2 18拦截器详解(九)

    ScopedModelDrivenInterceptor 该拦截器处于defaultStack第八的位置,其主要功能是从指定的作用域内检索相应的model设置到Action中,该类中有三个相关的属性: ...

  5. Struts2拦截器详解

    一.Struts2拦截器原理: Struts2拦截器的实现原理相对简单,当请求struts2的action时,Struts 2会查找配置文件,并根据其配置实例化相对的    拦截器对象,然后串成一个列 ...

  6. struts2 18拦截器详解(五)

    I18nInterceptor 该拦截器处理defaultStack第四的位置,是用来方便国际化的,如果说我们的一个Web项目要支持国际化的话,通常的做法是给定一个下拉框列出所支持的语言,当用户选择了 ...

  7. struts2内置拦截器和自定义拦截器详解(附源码)

    一.Struts2内置拦截器 Struts2中内置类许多的拦截器,它们提供了许多Struts2的核心功能和可选的高级特 性.这些内置的拦截器在struts-default.xml中配置.只有配置了拦截 ...

  8. 通俗易懂之SpringMVC&Struts2前端拦截器详解

    直接进入主题吧!一,配置Struts2的拦截器分两步走1配置对应的拦截器类:2在配置文件Struts.xml中进行配置拦截器同时在Strust2中配置拦截器类有三种方法1实现Interceptor接口 ...

  9. struts拦截器详解

    拦截器是Struts2最强大的特性之一,它是一种可以让用户在Action执行之前和Result执行之后进行一些功能处理的机制. 说到拦截器interceptor,就会想到过滤器filter: 过滤器f ...

随机推荐

  1. JAVA_SE基础——12.运算符的优先级

    优先级 操作符 含义 关联性 用法 ---------------------------------------------------------------- 1 [ ] 数组下标 左 arra ...

  2. KNN算法的代码实现

    # -*- coding: utf-8 -*-"""Created on Wed Mar 7 09:17:17 2018 @author: admin"&quo ...

  3. Python内置函数(28)——iter

    英文文档: iter(object[, sentinel]) Return an iterator object. The first argument is interpreted very dif ...

  4. 【笔记】HybridApp中使用Promise化的JS-Bridge

    背景: HybridApp,前端采用JS-bridge的方式调用Native的接口,如获取设备信息.拍照.人脸识别等 前端封装了调用库,每次调用Native接口,需要进行两步操作(1.在window下 ...

  5. spring MVC中定义异常页面

    如果我们在使用Spring MVC的过程中,想自定义异常页面的话,我们可以使用DispatcherServlet来指定异常页面,具体的做法很简单: 下面看我曾经的一个项目的spring配置文件: 1 ...

  6. python 爬取百度翻译进行中英互译

    感谢RoyFans 他的博客地址http://www.cnblogs.com/royfans/p/7417914.html import requests def py(): url = 'http: ...

  7. Django 相关

    Web框架本质 其实所有的Web应用本质就是一个socket服务端,而用户的浏览器就是一个socket客户端.简单的socket代码如下: import socket sk = socket.sock ...

  8. centos6.5时间相关

    时间同步 service ntpdate start 开启网络时间同步

  9. String、StringBuffer、StringBulider之间的联系和区别

    首先,我们大概总体的解释一下这三者的区别和联系 String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且大量浪费有限的内存空间. StringBuf ...

  10. Beautiful Soup常见的解析器

    Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快 ...