6. ModelDriven攔截器、Preparable 攔截器

1. 問題

  • Struts2 的 Action 我們將它定義為一個控制器,但是由于在 Action 中也可以來編寫一些業(yè)務(wù)邏輯,也有人會在 Action 輸入業(yè)務(wù)邏輯層。
  • 但是在企業(yè)開發(fā)中,我們一般會將業(yè)務(wù)邏輯層單獨編寫,而不是將它與 action 層寫到一起。
  • 之前的練習(xí)中,我們一直將屬性如 username 、 password 等保存在了 action 中。
  • 這樣做了以后導(dǎo)致我們在調(diào)用業(yè)務(wù)邏輯層時可能需要將Action的對象傳過去。
  • 但是這樣做無異于直接將 Servlet 傳給 Service,導(dǎo)致兩層之間的耦合,而且我們也不希望其他對象可以直接使用Action對象。
  • 要解決這個問題,我們可以采用的一種方式是,專門在Action中定義一個屬性,用來封裝信息。然后只需要將這個對象傳個service即可。
  • 但是這樣又帶來了一個新問題,誰來將屬性封裝進對象呢?答案就是ModelDriven攔截器
  • 攔截器:
    • 攔截器的作用和過濾器類似。
    • 攔截器可以在請求到達Action之前進行攔截,希望在請求到達Action之前通過攔截器做一些準(zhǔn)備工作。

  • Struts2 簡單的運行流程:
    • 請求首先到達StrutsPrepareAndExecuteFilter.doFilter()
    • 在doFilter方法中,先獲取ActionMapping
      • 判斷:如果ActionMapping為null,不是Struts請求,直接放行
      • 如果ActionMapping不為null,是Struts請求,繼續(xù)處理
    • 通過configurationManager加載Struts的配置信息,找到請求對應(yīng)的Action對象
      • 根據(jù)配置信息創(chuàng)建ActionProxy代理類。
    • StrutsActionProxy.execute()方法中調(diào)用DefaultActionInvocation.invoke()方法
    • 對所有的攔截器進行迭代在去分別調(diào)用攔截器intercept方法,進行攔截請求
    • intercept方法我們對請求進行一些處理,處理完畢以后繼續(xù)DefaultActionInvocation.invoke()方法
    • 如此反復(fù)直到所有的攔截器都被調(diào)用
    • 最后才去執(zhí)行Action的方法。
Struts2 運行流程 | center

2. 請求參數(shù)在哪封裝的:

  • 請求參數(shù)在到達Action之前,會先經(jīng)過ParametersInterceptor攔截器,
  • 在該攔截器中會自動對將請求參數(shù)封裝進值棧的棧頂對象,
  • 他會根據(jù)屬性名將屬性值設(shè)置進棧頂對象的相應(yīng)屬性中,
  • 如果棧頂中沒有該屬性,則繼續(xù)向第二對象進行封裝。
<!-- 查看 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); // 在這方法中設(shè)置參數(shù)到對象中
            } finally {
                ReflectionContextState.setCreatingNullObjects(contextMap, false);
                ReflectionContextState.setDenyMethodExecution(contextMap, false);
                ReflectionContextState.setReportingConversionErrors(contextMap, false);
            }
        }
    }
    return invocation.invoke();
}

3. ModelDriven攔截器

  • 雖然我們知道了參數(shù)是從什么地方壓入棧頂對象的,但是我們還是無法更好的處理方式把參數(shù)封裝成對象,放到對象中去。
  • 所以我們需要使用ModelDriven 攔截器
  • 我們查看 ModelDrivenInterceptor 源碼能發(fā)現(xiàn),在該攔截器中,對于實現(xiàn)了 ModelDriven 接口的Action ,會調(diào)用getModel() 方法把,getModel() 返回的對象壓入值棧棧頂,所以對于我們來說,可以使用一個JavaBean 作為參數(shù)的分裝
@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();
}
  • 當(dāng)用戶觸發(fā) add 請求時, ModelDriven 攔截器將調(diào)用 EmployeeAction 對象的 getModel() 方法, 并把返回的模型(Employee實例)壓入到 ValueStack 棧.
  • 接下來 Parameters 攔截器將把表單字段映射到 ValueStack 棧的棧頂對象的各個屬性中. 因為此時 ValueStack 棧的棧頂元素是剛被壓入的模型(Employee)對象, 所以該模型將被填充. 如果某個字段在模型里沒有匹配的屬性, Param 攔截器將嘗試 ValueStack 棧中的下一個對象
/**
 * 實現(xiàn) ModelDriven 接口
 */
public class EmployeeAction extends ActionSupport implements RequestAware, ModelDriven<Employee> 

//=================

// 重寫實現(xiàn) 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;
}
  • 但是我們還有一個問題需要解決, 我們?nèi)バ薷谋韱螖?shù)據(jù)的時候。通常需要使用Id 去數(shù)據(jù)庫中查詢出來對象。如果我們直接用下面的方式,在修改頁面是無法獲取到對應(yīng)的值。
    • 示例中我們只是使得 employee 引用復(fù)制了一份,并且指向了給 employeeDao.getEmpById(employee.getId());對象,實際上我們在棧頂兌現(xiàn)給的值并沒有改變
public String input() {
    // 因為點擊修改的時候,會put Id值到棧頂對象Employee 中,所以我們查詢出來的employee 并不會放到棧頂
    // 如果這樣的話 我們把指向 ValueStack 棧頂?shù)囊?,改為了從?shù)據(jù)庫查詢出來的值。
    // 所以 ValueStack 中的 employee 并沒有被賦值
    employee = employeeDao.getEmpById(employee.getId());
    
    System.out.println(employee);
    
    return "input";
}
  • 如圖所示| center
  • 但是我們?nèi)绻粋€個的去賦值屬性又特別麻煩.。如果使用獲取ValueStack 的方式去重新放入棧頂再彈出的方式也不是一個好的方法。

Employee emp = employeeDao.getEmpById(employee.getId());
employee.setName(emp.getName());
employee.setRole(emp.getRole());
employee.setDept(emp.getDept());
employee.setAge(emp.getAge());
  • 所以我們最好的方式,是在 getModel() 方法中,根據(jù) Action 的屬性 id 去數(shù)據(jù)庫中獲取資源。但是程序中默認使用的是 defaultStack 攔截器棧。在defaultStack攔截器棧中,modelDriven 攔截器之前并沒有 params 攔截器,如果需要對Action 參數(shù)賦值的話,就需要在 modelDriven 攔截器之前設(shè)置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>
  • 根據(jù)上面的結(jié)果,我們需要做的操作是:
    1. 先調(diào)用 param 攔截器封裝一部分屬性進入Action
    2. 再調(diào)用modelDriven 攔截器進入值棧
    3. 在 getModel() 方法中判斷id 是否為空,然后再返回不同的結(jié)果
  • 于是,我們可以使用 paramsPrepareParamsStack 攔截器棧,paramsPrepareParamsStack 這個攔截器棧中params攔截器會執(zhí)行兩次,
    • 一次在ModelDriven之前,一次在ModelDriven之后,
    • 這樣我們就可以根據(jù)不同的請求參數(shù),給值棧棧頂放入不同對象。
  • 在 struts.xml 修改默認的攔截器棧
<!-- 修改攔截器棧 -->
<interceptors>
    <interceptor-stack name="myInterceptorStck" >
        <interceptor-ref name="paramsPrepareParamsStack"></interceptor-ref>
    </interceptor-stack>
</interceptors>
    
<!-- 設(shè)置使用的攔截器為 我們設(shè)置的攔截器棧: 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 攔截器棧 之后,雖然已經(jīng)很簡潔了,但是我們出現(xiàn)了新的問題。
    • 在我們做 CRUD 操作是, deleteupdate 方法都帶有 id
      • 一個根據(jù) id 刪除用戶
      • 一個是更新用戶信息
    • 都有 id 就會導(dǎo)致調(diào)用getModel會去數(shù)據(jù)庫中查詢員工信息。
    • 但是實際業(yè)務(wù)上我們并沒有這樣的需求
    • 所以我們引出了 Preparable攔截器

5. Preparable 攔截器

  • Struts 2.0 中的 modelDriven 攔截器負責(zé)把 Action 類以外的一個對象壓入到值棧棧頂
  • 而 prepare 攔截器負責(zé)準(zhǔn)備為 getModel() 方法準(zhǔn)備 model, 并且在 modelDriven 攔截器之前
  • PrepareInterceptor攔截器使用方法
    • 若 Action 實現(xiàn) Preparable 接口,則 Action 方法需實現(xiàn) prepare() 方法
    • PrepareInterceptor 攔截器將調(diào)用 prepare() 方法,prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法
    • PrepareInterceptor 攔截器根據(jù) firstCallPrepareDo 屬性決定獲取 prepareActionMethodName 、prepareDoActionMethodName 的順序。默認情況下先獲取 prepareActionMethodName (), 如果沒有該方法,就尋找prepareDoActionMethodName()。如果找到對應(yīng)的方法就調(diào)用該方法
    • PrepareInterceptor 攔截器會根據(jù) alwaysInvokePrepare 屬性決定是否執(zhí)行 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;
             //根據(jù) firstCallPrepareDo 的值(默認為false)來決定調(diào)用兩個prepare 方法,可以在struts.xml 中修改
            if (firstCallPrepareDo) {
                prefixes = new String[] {ALT_PREPARE_PREFIX, PREPARE_PREFIX};
            } else {
                prefixes = new String[] {PREPARE_PREFIX, ALT_PREPARE_PREFIX};
            }
            //調(diào)用
            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;
            }
        }

        // 根據(jù) alwaysInvokePrepare  值(默認為true)來決定是否調(diào)用 Action 的 prepare()方法
        if (alwaysInvokePrepare) {
            ((Preparable) action).prepare();
        }
    }

    return invocation.invoke();
}
  • 方法執(zhí)行順序:
    • action.prepareXxx方法
    • action.prepare方法
    • action.getModel
    • action.action方法
  • 我們可以在prepareXxx方法做一個個性化的處理,可以在prepare方法做一些統(tǒng)一的處理。
public String add() {
    employeeDao.save(employee);
    return SUCCESS;
}
/**
 * 該方法會在add方法執(zhí)行之前執(zhí)行
 * 也會在getModel方法執(zhí)行之前執(zhí)行,放到值棧棧頂中
 */
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;
}
//======================================================
/**
 * 該方法中,可以做一些統(tǒng)一的處理
 */
@Override
public void prepare() throws Exception {
    
    System.out.println("prepare..................");
    
    /*
     *  例如:對于 delete 方法,雖然該方法只需要使用 id, 
     *      但是我們調(diào)用該方法之前,調(diào)用 getModel() 返回的是個 null
     *      所以我們可以在這 做一次判斷
     */
    if (employee == null) {
        employee = new Employee();
    }
}
  • 對于我們 如果想修改 PrepareInterceptor攔截器 中的一些參數(shù)。
    • 修改 prepareXxx 和 prepareDoXxxx 調(diào)用的順序
    • 修改 alwaysInvokePrepare 的值,使得 Action 不調(diào)用 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>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評論 19 139
  • 標(biāo)簽 如果要配置的標(biāo)簽,那么必須要先配置標(biāo)簽,代表的包的概念。 包含的屬性 name包的名稱,要求是唯一的,管理a...
    偷偷得路過閱讀 1,518評論 0 0
  • 概述 什么是Struts2的框架Struts2是Struts1的下一代產(chǎn)品,是在 struts1和WebWork的...
    inke閱讀 2,345評論 0 50
  • action中如何接受頁面?zhèn)鬟^來的參數(shù) 第一種情況:(同名參數(shù)) 例如:通過頁面要把id=1 name=tom a...
    清楓_小天閱讀 3,292評論 1 22
  • 兩節(jié)細胞生物學(xué)復(fù)習(xí)課 以前上課不認真,基本上算是廢的,復(fù)習(xí)課上著比正常上課還困難 集中不了注意力,決定自己看書 2...
    傷心不再來and快樂永存閱讀 353評論 0 0

友情鏈接更多精彩內(nèi)容