ModelDriven攔截器
首先要認識到,把Action和Model分隔開是有必要的。有些Action類不代表任何Model對象,他們的功能僅限於提供顯示服務。
如果Action實現了
ModelDriven
接口,該攔截器將把ModelDriven
接口的getModel()
方法返回的對象置於棧頂。
實現了ModelDriven後的Action類運行流程(Struts2源碼)
- 先執行
ModelDrivenInterceptor
的intercept
方法
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();
}
然後執行
ParametersInterceptor
的intercept
方法,該方法會把請求參數的值賦給棧頂對象對應的屬性,若棧頂對象沒有對應的屬性,則查詢值棧中下一個對象對應的屬性。注意
getModel
方法不能返回匿名對象,雖然返回一個匿名類也可以將其添加到棧頂,但是當前Action類的employee
成員變量卻是null
。也就是說成員變量的employee
和返回的匿名對象不是同一個對象。
public Employee getModel(){
return new Employee();
}
paramsPrepareParamsStack攔截器棧
paramsPrepareParamsStack
和paramsPrepareParamsStack
一樣都是攔截器棧。而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()
方法,將PrepareInterceptor
的alwaysInvokePrepare
屬性置爲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 {
//該方法以及不會被調用了
}
}