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 {
        //該方法以及不會被調用了
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章