Detailed explanation of modelDriven & prepare interceptor of Struts2

struts2 ModelDriven & Prepareable Interceptor

The construction of the development environment of Struts2, the configuration file and the value stack of Struts2 have been described earlier! In this blog post, we explain the use of Struts2 for CURD operation, the experience is different from that of Servlet, and this case understands ModelDriven and Prepareable interceptors!

Case process

  • Get database data and display it on employee-show.jsp page
  • On the employee-show.jsp page, you can add employee information to the database, edit and delete existing employee information
  • When the delete or edit request is passed to the action method and the empId of the employee to be operated is passed in as a parameter
  • When editing existing employee information, you need to echo the information to the form before editing.

Solutions

Employee-show.jsp

  • First, get all employee information and display it on the page. The following JSP page sends a request to send all employee information back to the page through Action class processing, and display it on the page ( for convenience, we store all data in a Map object, use Dao class to process )
  • Use in emp-show.jsp pageThe label handles the List of employee information returned from the action method and displays it.
  • As shown above, two hyperlinks are added to the end of each employee information line on the page as Edit and Delete .

Employee-edit.jsp

  • Click the edit hyperlink after an employee row , and the process is as described on the display page above. The wildcard mapping is used in the struts.xml file, so it will reach the Employee-edit.jsp page after being processed by the Action method.
  • When the edit hyperlink is clicked , the id of the operated employee will be passed into the action method, that is, the edit() method. The edit() method will obtain the corresponding employee information from the existing employee information and echo it back to the Employee -edit.jsp on the form
  • You can modify the employee information on the echoed page, click submit to execute the update() method, save the update in the Map , and jump to the display page to display the changed employee information in real time

emp-delete.action

  • Click the delete hyperlink after the employee row. Since deletion does not require any page, after the delete operation is performed, jump to emp-show.action to display the employee information after the operation.
  • When the delete hyperlink is clicked , the ID of the operated employee will be passed into the action method, that is, the delete() method. The delete() method deletes the corresponding employee information from the employee information database and then redirects to emp- show.action . Display deleted employee information

emp-add.action

  • Fill in the employee information to be added on the add form on the employee-show.jsp page and click submit to execute emp-show.action to get the new employee information list and display it
  • After clicking submit , save the employee information to a new object, execute the add() method to add the new object to the stored user list, and then redirect to emp-show.action to display the new employee information

Case catalog

  • The above directory, its code is as follows

    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>

think

  • As shown in the above code, we take the show(), add(), delete() methods in EmployeeCurd.java as examples, where the Employee object used in the add() method is initialized with the properties of the class, then the properties of the class are How is it initialized? How does the empId passed in when executing the delete method assign a value to the corresponding attribute?
    • It seems that we have not done any processing on these operations, but in fact, the params interceptor of struts2 has done all of them for us . The function of the params interceptor is to assign the value of the form attribute to the corresponding attribute of the object at the top of the stack, that is, add Before the () method is executed, the corresponding field value in the form is assigned to the stack top object (the stack top object defaults to the object of the Action class, that is, the EmployeeCurd object ).
    • When the delete operation is executed, the delete() method will operate the object according to the empId, which makes us need to obtain the incoming empId before executing the delete() method. We know that the params interceptor will assign values ​​according to the stack top attribute, but in The params in the default interceptor stack is after the ModelDriven interceptor, then you need to use the paramsPrepareParamsStack interceptor stack. Compared with the default interceptor, this interceptor will execute the params interceptor once before the ModelDriven interceptor is executed, and then Execute the params interceptor again, so that the getModel interceptor will use the incoming empId parameter, and we can also use whether the empId is empty to push the corresponding object on the top of the stack, that is, an empty object is required for the add operation, update When operating, you need to obtain the existing object and push it to the top of the stack according to empId for echoing
  • Configure and use the paramsPrepareParamsStack interceptor stack in the struts.xml file. The following configuration needs to be

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

echo question

  • When editing the existing employee information, the edit form will be echoed, because the form tag of struts2 will automatically find the matching attribute value in the value stack to echo, such as getModel after executing emp-edit.action The () method will push the object obtained from the Map collection to the top of the stack according to empId, then when the page is displayed, the corresponding object will be obtained from the top of the stack and assigned to the form label of struts2

shortcoming

  • There is redundancy in the properties in the EmployeeCurd and Employee classes, so how do we solve it? Struts2 default interceptor stack provides us with ModelDriven interceptor to solve this problem
  • accomplish
    • Action class implements ModelDriven
    • Advantages of using ModelDriven interceptors
      • There will be no redundancy between the Action class and the Model class, and the Action class is more concise

Action class that implements the ModelDriven interface

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;
    }
  • Detailed explanation
    • The ModelDriven interceptor uses the getModel() method to push the corresponding object to the top of the stack. For example , when the add() method is executed, the top of the stack is the employee object after the getModel() method is executed, so that the params interceptor can be used to convert the fields corresponding to the form. The attribute value is assigned to the attribute value corresponding to the object on the top of the stack
  • Source code parsing (intercept method of ModelDriven interceptor)

    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();
    }

PrepareInterceptor interceptor for advanced CURD operation

  • Problems with the above implementation:
    • When the delete operation is performed, the empId will be passed in , and the getModel() method judges that the empId is not empty, and will obtain an Employee object from the Map collection and place it on the top of the stack, but no object is required for the delete operation.
    • The getModel() method will create an empty Employee object on top of the stack when displaying all employees, which is not necessary for this operation
  • Solution - use PrepareInterceptor interceptor
  • accomplish
    • Action class implements the Preparable interface
  • View source code

    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;
      }
  • in conclusion
    • Read the source code to know that if the Action implements the Prepareable interface, Struts will try to execute the prepare[ActionMethodName] method. If the prepare[ActionMethodName] does not exist, it will try to execute the prepareDo[ActionMethodName] method. If neither exists, it will not be executed.
    • If the alwaysInvokePrepare property of PrepareInterceptor is false, Struts2 will not call the prepare() method of the Action that implements the Preparable interface, that is, prepare() can not be implemented but prepare a prepareXxx or prepareDoXxx method for each Action method , and then alwaysInvokePrepare The property is set to false, then the prepare method will not be triggered each time it is executed
    • If this interface is implemented, then each prepareXxx method will prepare a Model for the corresponding Xxx method, and use the getModel() method to place it on the top of the stack without empId to judge, which affects the program efficiency. To put it bluntly, the prepareXxx method prepares the return object for the getModel method
  • Final code (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");
        }
    }
  • At this time, the action method of the Action class is very concise, and there will be no other redundant problems.
  • Configure the alwaysInvokePrepare property to false in the struts.xml file, as follows:

    <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"/> 

So far, this is the case about ModelDriven and prepare interceptor. If there are any problems and insufficient expressions, please point out, thank you!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324837628&siteId=291194637