6. ModelDriven攔截器、Preparable 攔截器

1. 問(wèn)題

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

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

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

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

3. ModelDriven攔截器

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

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

// 重寫(xiě)實(shí)現(xiàn) getModel 方法
@Override
public Employee getModel() {

    System.out.println("  getModel() Method, ............");

    if (employee == null) {
        employee = new Employee();
    } 
    // 如果通過(guò) return new Employee() 的方式,雖然棧頂對(duì)象是Employee,但是在Action 中的Employee為Null
    return employee;
}
  • 但是我們還有一個(gè)問(wèn)題需要解決, 我們?nèi)バ薷谋韱螖?shù)據(jù)的時(shí)候。通常需要使用Id 去數(shù)據(jù)庫(kù)中查詢(xún)出來(lái)對(duì)象。如果我們直接用下面的方式,在修改頁(yè)面是無(wú)法獲取到對(duì)應(yīng)的值。
    • 示例中我們只是使得 employee 引用復(fù)制了一份,并且指向了給 employeeDao.getEmpById(employee.getId());對(duì)象,實(shí)際上我們?cè)跅m攦冬F(xiàn)給的值并沒(méi)有改變
public String input() {
    // 因?yàn)辄c(diǎn)擊修改的時(shí)候,會(huì)put Id值到棧頂對(duì)象Employee 中,所以我們查詢(xún)出來(lái)的employee 并不會(huì)放到棧頂
    // 如果這樣的話(huà) 我們把指向 ValueStack 棧頂?shù)囊茫臑榱藦臄?shù)據(jù)庫(kù)查詢(xún)出來(lái)的值。
    // 所以 ValueStack 中的 employee 并沒(méi)有被賦值
    employee = employeeDao.getEmpById(employee.getId());
    
    System.out.println(employee);
    
    return "input";
}
  • 如圖所示| center
  • 但是我們?nèi)绻粋€(gè)個(gè)的去賦值屬性又特別麻煩.。如果使用獲取ValueStack 的方式去重新放入棧頂再?gòu)棾龅姆绞揭膊皇且粋€(gè)好的方法。

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ù)庫(kù)中獲取資源。但是程序中默認(rèn)使用的是 defaultStack 攔截器棧。在defaultStack攔截器棧中,modelDriven 攔截器之前并沒(méi)有 params 攔截器,如果需要對(duì)Action 參數(shù)賦值的話(huà),就需要在 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 攔截器封裝一部分屬性進(jìn)入Action
    2. 再調(diào)用modelDriven 攔截器進(jìn)入值棧
    3. 在 getModel() 方法中判斷id 是否為空,然后再返回不同的結(jié)果
  • 于是,我們可以使用 paramsPrepareParamsStack 攔截器棧,paramsPrepareParamsStack 這個(gè)攔截器棧中params攔截器會(huì)執(zhí)行兩次,
    • 一次在ModelDriven之前,一次在ModelDriven之后,
    • 這樣我們就可以根據(jù)不同的請(qǐng)求參數(shù),給值棧棧頂放入不同對(duì)象。
  • 在 struts.xml 修改默認(rèn)的攔截器棧
<!-- 修改攔截器棧 -->
<interceptors>
    <interceptor-stack name="myInterceptorStck" >
        <interceptor-ref name="paramsPrepareParamsStack"></interceptor-ref>
    </interceptor-stack>
</interceptors>
    
<!-- 設(shè)置使用的攔截器為 我們?cè)O(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";
}
  • 這樣使得代碼就更加的簡(jiǎn)潔了一些
  • 流程

4. 新的問(wèn)題

  • 在使用 paramsPrepareParamsStack 攔截器棧 之后,雖然已經(jīng)很簡(jiǎn)潔了,但是我們出現(xiàn)了新的問(wèn)題。
    • 在我們做 CRUD 操作是, deleteupdate 方法都帶有 id
      • 一個(gè)根據(jù) id 刪除用戶(hù)
      • 一個(gè)是更新用戶(hù)信息
    • 都有 id 就會(huì)導(dǎo)致調(diào)用getModel會(huì)去數(shù)據(jù)庫(kù)中查詢(xún)員工信息。
    • 但是實(shí)際業(yè)務(wù)上我們并沒(méi)有這樣的需求
    • 所以我們引出了 Preparable攔截器

5. Preparable 攔截器

  • Struts 2.0 中的 modelDriven 攔截器負(fù)責(zé)把 Action 類(lèi)以外的一個(gè)對(duì)象壓入到值棧棧頂
  • 而 prepare 攔截器負(fù)責(zé)準(zhǔn)備為 getModel() 方法準(zhǔn)備 model, 并且在 modelDriven 攔截器之前
  • PrepareInterceptor攔截器使用方法
    • 若 Action 實(shí)現(xiàn) Preparable 接口,則 Action 方法需實(shí)現(xiàn) prepare() 方法
    • PrepareInterceptor 攔截器將調(diào)用 prepare() 方法,prepareActionMethodName()方法 或 prepareDoActionMethodName ()方法
    • PrepareInterceptor 攔截器根據(jù) firstCallPrepareDo 屬性決定獲取 prepareActionMethodName 、prepareDoActionMethodName 的順序。默認(rèn)情況下先獲取 prepareActionMethodName (), 如果沒(méi)有該方法,就尋找prepareDoActionMethodName()。如果找到對(duì)應(yīng)的方法就調(diào)用該方法
    • PrepareInterceptor 攔截器會(huì)根據(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 的值(默認(rèn)為false)來(lái)決定調(diào)用兩個(gè)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  值(默認(rèn)為true)來(lái)決定是否調(diào)用 Action 的 prepare()方法
        if (alwaysInvokePrepare) {
            ((Preparable) action).prepare();
        }
    }

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

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

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

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