• 6. ModelDriven拦截器、Preparable 拦截器


    1. 问题

    • Struts2 的 Action 我们将它定义为一个控制器,但是由于在 Action 中也可以来编写一些业务逻辑,也有人会在 Action 输入业务逻辑层。
    • 但是在企业开发中,我们一般会将业务逻辑层单独编写,而不是将它与 action 层写到一起。
    • 之前的练习中,我们一直将属性如 username 、 password 等保存在了 action 中。
    • 这样做了以后导致我们在调用业务逻辑层时可能需要将Action的对象传过去。
    • 但是这样做无异于直接将 Servlet 传给 Service,导致两层之间的耦合,而且我们也不希望其他对象可以直接使用Action对象。
    • 要解决这个问题,我们可以采用的一种方式是,专门在Action中定义一个属性,用来封装信息。然后只需要将这个对象传个service即可。
    • 但是这样又带来了一个新问题,谁来将属性封装进对象呢?答案就是ModelDriven拦截器
    • 拦截器:
      • 拦截器的作用和过滤器类似。
      • 拦截器可以在请求到达Action之前进行拦截,希望在请求到达Action之前通过拦截器做一些准备工作。

    • Struts2 简单的运行流程:
      • 请求首先到达StrutsPrepareAndExecuteFilter.doFilter()
      • 在doFilter方法中,先获取ActionMapping
        • 判断:如果ActionMapping为null,不是Struts请求,直接放行
        • 如果ActionMapping不为null,是Struts请求,继续处理
      • 通过configurationManager加载Struts的配置信息,找到请求对应的Action对象
        • 根据配置信息创建ActionProxy代理类。
      • StrutsActionProxy.execute()方法中调用DefaultActionInvocation.invoke()方法
      • 对所有的拦截器进行迭代在去分别调用拦截器intercept方法,进行拦截请求
      • intercept方法我们对请求进行一些处理,处理完毕以后继续DefaultActionInvocation.invoke()方法
      • 如此反复直到所有的拦截器都被调用
      • 最后才去执行Action的方法。

    Struts2 运行流程 | center


    2. 请求参数在哪封装的:

    • 请求参数在到达Action之前,会先经过ParametersInterceptor拦截器,
    • 在该拦截器中会自动对将请求参数封装进值栈的栈顶对象,
    • 他会根据属性名将属性值设置进栈顶对象的相应属性中,
    • 如果栈顶中没有该属性,则继续向第二对象进行封装。
    <!-- 查看 ParametersInterceptor 源码-->
    @Override
    public String doIntercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
        if (!(action instanceof NoParameters)) {
            ActionContext ac = invocation.getInvocationContext();
            final Map<String, Object> parameters = retrieveParameters(ac);
    
            if (LOG.isDebugEnabled()) {
                LOG.debug("Setting params " + getParameterLogMap(parameters));
            }
    
            if (parameters != null) {
                Map<String, Object> contextMap = ac.getContextMap();
                try {
                    ReflectionContextState.setCreatingNullObjects(contextMap, true);
                    ReflectionContextState.setDenyMethodExecution(contextMap, true);
                    ReflectionContextState.setReportingConversionErrors(contextMap, true);
    
                    ValueStack stack = ac.getValueStack();
                    setParameters(action, stack, parameters); // 在这方法中设置参数到对象中
                } finally {
                    ReflectionContextState.setCreatingNullObjects(contextMap, false);
                    ReflectionContextState.setDenyMethodExecution(contextMap, false);
                    ReflectionContextState.setReportingConversionErrors(contextMap, false);
                }
            }
        }
        return invocation.invoke();
    }
    

    3. ModelDriven拦截器

    • 虽然我们知道了参数是从什么地方压入栈顶对象的,但是我们还是无法更好的处理方式把参数封装成对象,放到对象中去。
    • 所以我们需要使用ModelDriven 拦截器
    • 我们查看 ModelDrivenInterceptor 源码能发现,在该拦截器中,对于实现了 ModelDriven 接口的Action ,会调用getModel() 方法把,getModel() 返回的对象压入值栈栈顶,所以对于我们来说,可以使用一个JavaBean 作为参数的分装
    @Override
    public String intercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
    
        if (action instanceof ModelDriven) {
            ModelDriven modelDriven = (ModelDriven) action;
            ValueStack stack = invocation.getStack();
            Object model = modelDriven.getModel();
            if (model !=  null) {
               stack.push(model);
            }
            if (refreshModelBeforeResult) {
                invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
            }
        }
        return invocation.invoke();
    }
    
    • 当用户触发 add 请求时, ModelDriven 拦截器将调用 EmployeeAction 对象的 getModel() 方法, 并把返回的模型(Employee实例)压入到 ValueStack 栈.
    • 接下来 Parameters 拦截器将把表单字段映射到 ValueStack 栈的栈顶对象的各个属性中. 因为此时 ValueStack 栈的栈顶元素是刚被压入的模型(Employee)对象, 所以该模型将被填充. 如果某个字段在模型里没有匹配的属性, Param 拦截器将尝试 ValueStack 栈中的下一个对象
    /**
     * 实现 ModelDriven 接口
     */
    public class EmployeeAction extends ActionSupport implements RequestAware, ModelDriven<Employee> 
    
    //=================
    
    // 重写实现 getModel 方法
    @Override
    public Employee getModel() {
    
    	System.out.println("  getModel() Method, ............");
    
    	if (employee == null) {
    		employee = new Employee();
    	} 
    	// 如果通过 return new Employee() 的方式,虽然栈顶对象是Employee,但是在Action 中的Employee为Null
    	return employee;
    }
    
    • 但是我们还有一个问题需要解决, 我们去修改表单数据的时候。通常需要使用Id 去数据库中查询出来对象。如果我们直接用下面的方式,在修改页面是无法获取到对应的值。
      • 示例中我们只是使得 employee 引用复制了一份,并且指向了给 employeeDao.getEmpById(employee.getId());对象,实际上我们在栈顶兑现给的值并没有改变
    public String input() {
    	// 因为点击修改的时候,会put Id值到栈顶对象Employee 中,所以我们查询出来的employee 并不会放到栈顶
    	// 如果这样的话 我们把指向 ValueStack 栈顶的引用,改为了从数据库查询出来的值。
    	// 所以 ValueStack 中的 employee 并没有被赋值
    	employee = employeeDao.getEmpById(employee.getId());
    	
    	System.out.println(employee);
    	
    	return "input";
    }
    
    • 如图所示| center

    • 但是我们如果一个个的去赋值属性又特别麻烦.。如果使用获取ValueStack 的方式去重新放入栈顶再弹出的方式也不是一个好的方法

    Employee emp = employeeDao.getEmpById(employee.getId());
    employee.setName(emp.getName());
    employee.setRole(emp.getRole());
    employee.setDept(emp.getDept());
    employee.setAge(emp.getAge());
    
    • 所以我们最好的方式,是在 getModel() 方法中,根据 Action 的属性 id 去数据库中获取资源。但是程序中默认使用的是 defaultStack 拦截器栈。在defaultStack拦截器栈中,modelDriven 拦截器之前并没有 params 拦截器,如果需要对Action 参数赋值的话,就需要在 modelDriven 拦截器之前设置params 拦截器
     <interceptor-stack name="defaultStack">
        <interceptor-ref name="exception"/>
        <interceptor-ref name="alias"/>
        <interceptor-ref name="servletConfig"/>
        <interceptor-ref name="i18n"/>
        <interceptor-ref name="prepare"/>
        <interceptor-ref name="chain"/>
        <interceptor-ref name="scopedModelDriven"/>
        <interceptor-ref name="modelDriven"/>
        <interceptor-ref name="fileUpload"/>
        <interceptor-ref name="checkbox"/>
        <interceptor-ref name="datetime"/>
        <interceptor-ref name="multiselect"/>
        <interceptor-ref name="staticParams"/>
        <interceptor-ref name="actionMappingParams"/>
        <interceptor-ref name="params"/>
        <interceptor-ref name="conversionError"/>
        <interceptor-ref name="validation">
            <param name="excludeMethods">input,back,cancel,browse</param>
        </interceptor-ref>
        <interceptor-ref name="workflow">
            <param name="excludeMethods">input,back,cancel,browse</param>
        </interceptor-ref>
        <interceptor-ref name="debugging"/>
        <interceptor-ref name="deprecation"/>
    </interceptor-stack>
    
    • 根据上面的结果,我们需要做的操作是:
      1. 先调用 param 拦截器封装一部分属性进入Action
      2. 再调用modelDriven 拦截器进入值栈
      3. 在 getModel() 方法中判断id 是否为空,然后再返回不同的结果
    • 于是,我们可以使用 paramsPrepareParamsStack 拦截器栈,paramsPrepareParamsStack 这个拦截器栈中params拦截器会执行两次,
      • 一次在ModelDriven之前,一次在ModelDriven之后,
      • 这样我们就可以根据不同的请求参数,给值栈栈顶放入不同对象。
    • 在 struts.xml 修改默认的拦截器栈
    <!-- 修改拦截器栈 -->
    <interceptors>
    	<interceptor-stack name="myInterceptorStck" >
    		<interceptor-ref name="paramsPrepareParamsStack"></interceptor-ref>
    	</interceptor-stack>
    </interceptors>
    	
    <!-- 设置使用的拦截器为 我们设置的拦截器栈: myInterceptorStck  -->
    <default-interceptor-ref name="myInterceptorStck"></default-interceptor-ref>
    
    • 使用paramsPrepareParamsStack 拦截器栈后,修改后的 getModel()input 方法
    @Override
    public Employee getModel() {
    	
    	System.out.println(" getModel() Method ............");
    
    	if (id == null) {
    		employee = new Employee();
    	} else {
    		employee = employeeDao.getEmpById(id);
    	}
    	return employee;
    }
    
    // ====================
    public String input() {
    	return "input";
    }
    
    • 这样使得代码就更加的简洁了一些
    • 流程

    4. 新的问题

    • 在使用 paramsPrepareParamsStack 拦截器栈 之后,虽然已经很简洁了,但是我们出现了新的问题。
      • 在我们做 CRUD 操作是, deleteupdate 方法都带有 id
        • 一个根据 id 删除用户
        • 一个是更新用户信息
      • 都有 id 就会导致调用getModel会去数据库中查询员工信息
      • 但是实际业务上我们并没有这样的需求
      • 所以我们引出了 Preparable拦截器

    5. Preparable 拦截器

    • Struts 2.0 中的 modelDriven 拦截器负责把 Action 类以外的一个对象压入到值栈栈顶
    • 而 prepare 拦截器负责准备为 getModel() 方法准备 model, 并且在 modelDriven 拦截器之前
    • PrepareInterceptor拦截器使用方法
      • 若 Action 实现 Preparable 接口,则 Action 方法需实现 prepare() 方法
      • PrepareInterceptor 拦截器将调用 prepare() 方法,prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法
      • PrepareInterceptor 拦截器根据 firstCallPrepareDo 属性决定获取 prepareActionMethodName 、prepareDoActionMethodName 的顺序。默认情况下先获取 prepareActionMethodName (), 如果没有该方法,就寻找prepareDoActionMethodName()。如果找到对应的方法就调用该方法
      • PrepareInterceptor 拦截器会根据 alwaysInvokePrepare 属性决定是否执行 prepare() 方法
    // com.opensymphony.xwork2.interceptor.PrepareInterceptor
    private final static String PREPARE_PREFIX = "prepare";
    private final static String ALT_PREPARE_PREFIX = "prepareDo";
    
    private boolean alwaysInvokePrepare = true;
    private boolean firstCallPrepareDo = false;
    //===============
    @Override
    public String doIntercept(ActionInvocation invocation) throws Exception {
        Object action = invocation.getAction();
    
        if (action instanceof Preparable) {
            try {
                String[] prefixes;
                 //根据 firstCallPrepareDo 的值(默认为false)来决定调用两个prepare 方法,可以在struts.xml 中修改
                if (firstCallPrepareDo) {
                    prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
                } else {
                    prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
                }
                //调用
                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();
    }
    
    • 方法执行顺序:
      • action.prepareXxx方法
      • action.prepare方法
      • action.getModel
      • action.action方法
    • 我们可以在prepareXxx方法做一个个性化的处理,可以在prepare方法做一些统一的处理
    public String add() {
    	employeeDao.save(employee);
    	return SUCCESS;
    }
    /**
     * 该方法会在add方法执行之前执行
     * 也会在getModel方法执行之前执行,放到值栈栈顶中
     */
    public void prepareDoAdd() {
    	System.out.println("prepareDoAdd............");
    	employee = new Employee();
    }
    
    //======================================================
    public String del() {
    	employeeDao.delete(employee.getId());
    	return SUCCESS;
    }
    //======================================================
    public String input() {
    	return "input";
    }
    public void prepareInput() {
    	System.out.println("prepareInput.........");
    	employee = employeeDao.getEmpById(id);
    }
    //======================================================
    public String update() {
    	employeeDao.update(employee);
    	return SUCCESS;
    }
    /**
     * 在update方法之前,初始化employ,然后由 getModel 方法放到栈顶
     */
    public void prepareUpdate() {
    	System.out.println("prepareUpdate..............");
    	employee = new Employee();
    }
    //======================================================
    @Override
    public Employee getModel() {
    	System.out.println("getModel。。。。。。。。。。。");
    	return employee;
    }
    //======================================================
    /**
     * 该方法中,可以做一些统一的处理
     */
    @Override
    public void prepare() throws Exception {
    	
    	System.out.println("prepare..................");
    	
    	/*
    	 *  例如:对于 delete 方法,虽然该方法只需要使用 id, 
    	 *  	但是我们调用该方法之前,调用 getModel() 返回的是个 null
    	 *  	所以我们可以在这 做一次判断
    	 */
    	if (employee == null) {
    		employee = new Employee();
    	}
    }
    
    • 对于我们 如果想修改 PrepareInterceptor拦截器 中的一些参数。
      • 修改 prepareXxx 和 prepareDoXxxx 调用的顺序
      • 修改 alwaysInvokePrepare 的值,使得 Action 不调用 prepare() 方法
      • 参考 docs 中 Interceptor Parameter Overriding
    <!-- 修改拦截器栈 -->
    <interceptors>
    	<interceptor-stack name="myInterceptorStck" >
    		<interceptor-ref name="paramsPrepareParamsStack">
    			<!-- 修改拦截器栈属性值,name.filed -->
    			<param name="prepare.alwaysInvokePrepare">false</param>
    			<param name="prepare.firstCallPrepareDo">true</param>
    		</interceptor-ref>
    	</interceptor-stack>
    </interceptors>
    
    人生如棋,我愿为为卒;行走虽慢,可曾见我后退一步!
  • 相关阅读:
    word设置的密码忘了怎么办?
    Navicat Report Viewer 设置 HTTP 的方法
    如何处理Navicat Report Viewer 报表
    excel密码忘记了怎么办
    Beyond Compare文本比较搜索功能详解
    Popular Cows POJ
    Problem B. Harvest of Apples HDU
    网络流模型整理
    The Shortest Statement CodeForces
    Vasya and Multisets CodeForces
  • 原文地址:https://www.cnblogs.com/MPPC/p/6123088.html
Copyright © 2020-2023  润新知