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é)果,我們需要做的操作是:
- 先調(diào)用 param 攔截器封裝一部分屬性進(jìn)入Action
- 再調(diào)用
modelDriven攔截器進(jìn)入值棧 - 在 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操作是,delete和update方法都帶有 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>

