Struts2学习笔记 | ModelDriven拦截器和paramsPrepareParamsStack拦截器栈解析

ModelDriven拦截器

  • 首先要认识到,把Action和Model分隔开是有必要的。有些Action类不代表任何Model对象,他们的功能仅限于提供显示服务。

  • 如果Action实现了ModelDriven接口,该拦截器将把ModelDriven接口的getModel()方法返回的对象置于栈顶。

实现了ModelDriven后的Action类运行流程(Struts2源码)

  • 先执行ModelDrivenInterceptorintercept方法
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()方法
            //即调用EmployeeAction的getModel()方法
            Object model = modelDriven.getModel();
            if (model != null) {
                //把getModel()方法的返回值压入到值栈的栈顶,实际压入的是EmployeeAction的Employee的成员变量
                stack.push(model);
            }

            if (this.refreshModelBeforeResult) {
                invocation.addPreResultListener(new ModelDrivenInterceptor.RefreshModelBeforeResult(modelDriven, model));
            }
        }

        return invocation.invoke();
    }
  • 然后执行ParametersInterceptorintercept方法,该方法会把请求参数的值赋给栈顶对象对应的属性,若栈顶对象没有对应的属性,则查询值栈中下一个对象对应的属性。

  • 注意
    getModel方法不能返回匿名对象,虽然返回一个匿名类也可以将其添加到栈顶,但是当前Action类的employee成员变量却是null。也就是说成员变量的employee和返回的匿名对象不是同一个对象。

public Employee getModel(){
    return new Employee();
}

paramsPrepareParamsStack拦截器栈

  • paramsPrepareParamsStackparamsPrepareParamsStack一样都是拦截器栈。而struts-default包默认使用paramsPrepareParamsStack拦截器栈。

  • 若要使用paramsPrepareParamsStack,则需要在struts.xml文件中配置使用paramsPrepareParams作为默认的拦截器栈,在struts.xml文件的package节点内配置<default-interceptor-ref name="paramsPrepareParamsStack"></default-interceptor-ref>,配置后则是使用paramsPrepareParamsStack作为默认的拦截器栈。

  • paramsPrepareParamsStack拦截器栈中的拦截器的调用顺序是先运行params,再运行modelDriven,最后再次运行params因此可以先把请求参数赋给Action对应的属性,再根据 赋给Action的那个属性值 决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值

小Demo:进行edit操作,即表单的编辑(更新)操作
Action类的代码:

package struts.crud;

import com.opensymphony.xwork2.ModelDriven;
import org.apache.struts2.interceptor.RequestAware;

import java.util.Map;
/**
 * 该代码存在的问题:
 * 1.在删除的时候,employeeId肯定不为null,但getModel方法却从数据库中加载了一个对象(多余的步骤)
 * 2.执行查询全部信息时,也创建了个Employee对象(浪费)
 * 
 */
public class EmployeeAction implements RequestAware, ModelDriven<Employee> {
    private Dao dao = new Dao();
    private Employee employee;
    private Map<String,Object> requests;
    //需要在当前的EmployeeAction中定义employeeId属性,用来接收请求参数
    private Integer employeeId;

    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }

    /**
     * 在此代码中:
     * 先为EmployeeAction的employeeId赋值(在jsp页面已经传过来了,此处忽略)
     * 再根据employeeId从数据库中加载对应的对象放入到值栈中
     * 再为栈顶对象的employeeId赋值(实际上此时employeeId已经存在)
     * 再把栈顶对象的属性回显到表单中
     * @return
     */
    public String edit(){
        return "edit";
    }
    public String delete(){
        dao.delete(employeeId);
        return "success";
    }

    public String save(){
        dao.save(employee);
        return "success";
    }
    public String update(){
        dao.update(employee);
        return "success";
    }
    public String list(){
        requests.put("emps",dao.getEmployees());
        return "list";
    }

    @Override
    public void setRequest(Map < String, Object > map) {
        requests = map;
    }

    @Override
    public Employee getModel() {
        //employeeId为空,则是新建操作,则要重新new一个Employee对象并返回
        if(employeeId == null)
            employee = new Employee();
        else//若不为空,则是更新操作,则直接从数据库中拿到该对象并返回即可
            employee = dao.get(employeeId);

        return employee;
    }
}

关于表单回显

Struts2表单标签会从值栈中获取对应的属性值进行回显


perpareInterceptor拦截器

上面我们提到的modelDriven拦截器是负责把Action类以外的一个对象压入到值栈栈顶,而prepare拦截器负责准备为getModel()方法准备model

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

  • alwaysInvokePrepare属性为false,则Struts2将不会调用实现了Preparable接口的Action的prepare()方法

  • 源码解析

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 (this.firstCallPrepareDo) {
                    prefixes = new String[]{"prepareDo", "prepare"};
                } else {
                    prefixes = new String[]{"prepare", "prepareDo"};
                }
                //若为false,则prefixes:"prepare", "prepareDo"
                //调用前缀方法
                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)决定是否调用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);
        //若方法不为null,则通过反射调用前缀方法
        if (method != null) {
            method.invoke(action);
        }

    }
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$];
            //通过拼接的方式,得到前缀方法名,第一次为prepare+[方法名],第二次为prepareDo+[方法名]
            String prefixedMethodName = prefixe + capitalizedMethodName;

            try {
                //利用反射从action中获取对应的方法,若有则返回,并结束循环。
                return action.getClass().getMethod(prefixedMethodName, EMPTY_CLASS_ARRAY);
            } catch (NoSuchMethodException var10) {
                LOG.debug("Cannot find method [{}] in action [{}]", prefixedMethodName, action);
                ++i$;
            }
        }

        return null;
    }

所以现在,对于之前paramsPrepareParamsStack拦截器栈的Demo中存在的问题。可以这么解决:
可以为每一个ActionMethod准备prepare[ActionMethodName]方法,而抛弃原来的prepare()方法,将PrepareInterceptoralwaysInvokePrepare属性置为false,以避免Struts2框架再调用prepare()方法。

  • 在配置文件中为拦截器栈的属性赋值
    struts.xml文件中使用如下来设置:
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE struts PUBLIC
        "-//Apache Software Foundation//DTD Struts Configuration 2.5//EN"
        "http://struts.apache.org/dtds/struts-2.5.dtd">

<struts>
    <package name="default" extends="struts-default" strict-method-invocation="false">
        <!-- 修改PrepareInterceptor拦截器的alwaysInvokePrepare属性为false-->
        <interceptors>
            <!-- 名字自取-->
            <interceptor-stack name="cerrStack">
                <!-- 指向的拦截器栈 paramsPrepareParamsStack-->
                <interceptor-ref name="paramsPrepareParamsStack">
                    <param name="prepare.alwaysInvokePrepare">false</param>
                </interceptor-ref>
            </interceptor-stack>
        </interceptors>
        <!-- 配置使用cerrStack(自己配置的)作为默认的拦截器栈-->
        <default-interceptor-ref name="cerrStack"></default-interceptor-ref>
    </package>
</struts>

重新编写之后的Action类文件如下:

package struts.crud;

import com.opensymphony.xwork2.ModelDriven;
import com.opensymphony.xwork2.Preparable;
import org.apache.struts2.interceptor.RequestAware;

import java.util.Map;

public class EmployeeAction implements RequestAware, ModelDriven<Employee>, Preparable {
    private Dao dao = new Dao();
    private Employee employee;
    private Map<String,Object> requests;
    //需要在当前的EmployeeAction中定义employeeId属性,用来接收请求参数
    private Integer employeeId;

    public void setEmployeeId(Integer employeeId) {
        this.employeeId = employeeId;
    }

    /**
     * 为edit做准备
     */
    public void prepareEdit(){
        employee = dao.get(employeeId);
    }

    public String edit(){
        return "edit";
    }
    public String delete(){
        dao.delete(employeeId);
        return "success";
    }

    /**
     * 为save()做准备
     */
    public void prepareSave(){
        employee = new Employee();
    }

    public String save(){
        dao.save(employee);
        return "success";
    }

    public void prepareUpdate(){
        employee = new Employee();
    }
    public String update(){
        dao.update(employee);
        return "success";
    }
    public String list(){
        requests.put("emps",dao.getEmployees());
        return "list";
    }

    @Override
    public void setRequest(Map < String, Object > map) {
        requests = map;
    }

    @Override
    public Employee getModel() {
        return employee;
    }

    /**
     * 作用:为getModel()方法准备model的
     * @throws Exception
     */
    @Override
    public void prepare() throws Exception {
        //该方法以及不会被调用了
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值