Struts2中的ModelDriven機制及其運用、refreshModelBeforeResult屬性解決的問題



1.為什么需要ModelDriven?
所謂ModelDriven,意思是直接把實體類當成頁面數據的收集對象。比如,有實體類User如下:


package cn.com.leadfar.struts2.actions;
public class User {
private int id;
private String username;
private String password;
private int age;
private String address;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}


假如要寫一個Action,用來添加User。
第一種做法是直接在Action中定義所有需要的屬性,然后在JSP中直接用屬性名稱來提交數據:
UserAction:


public class UserAction {
private int id;
private String username;
private String password;
private int age;
private String address;
public String add(){
User user = new User();
user.setId(id);
user.setUsername(username);
user.setPassword(password);
user.setAge(age);
user.setAddress(address);
new UserManager().addUser(user);
return "success";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}


add_input.jsp:


<form action="test/user.action" method="post">
<input type="hidden" name="method:add">
username:<input type="text" name="username"> <br/>
password:<input type="text" name="password"> <br/>
age:<input type="text" name="age"> <br/>
address:<input type="text" name="address"> <br/>
<input type="submit" name="submit" value="添加用戶">
</form> <br/>


上述做法不好之處是:如果實體類的屬性非常多,那么Action中也要定義相同的屬性。
第二種做法是將User對象定義到UserAction中,然后在JSP中通過user屬性來給user賦值:
UserAction:


public class UserAction {
private User user;
public String add(){
new UserManager().addUser(user);
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}


add_input.jsp:


<form action="test/user.action" method="post">
<input type="hidden" name="method:add">
username:<input type="text" name="user.username"> <br/>
password:<input type="text" name="user.password"> <br/>
age:<input type="text" name="user.age"> <br/>
address:<input type="text" name="user.address"> <br/>
<input type="submit" name="submit" value="添加用戶">
</form> <br/>


這種做法不好的地方是:JSP頁面上表單域中的命名變得太長
第三種做法是利用ModelDriven機制,讓UserAction實現一個ModelDriven接口,同時實現接口中的方法:getModel()。如下所示:


public class UserAction implements ModelDriven{
private User user;
@Override
public Object getModel() {
if(user == null){
user = new User();
}
return user;
}
public String add(){
new UserManager().addUser(user);
return "success";
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}


JSP的代碼如下:


<form action="test/user.action" method="post">
<input type="hidden" name="method:add">
username:<input type="text" name="username"> <br/>
password:<input type="text" name="password"> <br/>
age:<input type="text" name="age"> <br/>
<input type="submit" name="submit" value="添加用戶">
</form> <br/>


可見,第三種做法是比較好的,Action和JSP寫起來都比較簡單。
2.ModelDriven背后的機制?
ModelDriven背后的機制就是ValueStack。界面通過:username/age/address這樣的名稱,就能夠被直接賦值給user對象,這證明user對象正是ValueStack中的一個root對象!
那么,為什么user對象會在ValueStack中呢?它是什么時候被壓入ValueStack的呢?答案是:ModelDrivenInterceptor(關於Interceptor的概念,請參考后續章節的說明)。ModelDrivenInterceptor是缺省的攔截器鏈的一部分,當一個請求經過ModelDrivenInterceptor的時候,在這個攔截器中,會判斷當前要調用的Action對象是否實現了ModelDriven接口,如果實現了這個接口,則調用getModel()方法,並把返回值(本例是返回user對象)壓入ValueStack。
請看ModelDrivenInterceptor的代碼:


public class ModelDrivenInterceptor extends AbstractInterceptor {
protected boolean refreshModelBeforeResult = false;
public void setRefreshModelBeforeResult(boolean val) {
this.refreshModelBeforeResult = val;
}
@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();
}


從ModelDrivenInterceptor中,即可以看到model對象被壓入ValueStack中!
其中的refreshModelBeforeResult是為了接下來描述的一個問題而提供的解決方法。
理解常見的陷阱及解決辦法
假設我們要更新一個實體對象,那么第一步首先是打開更新界面,請看下述模擬打開更新界面的代碼:


public class UserAction implements ModelDriven{
private User user;
@Override
public Object getModel() {
if(user == null){
user = new User();
//user.setUsername("這是原來的User對象");
}
return user;
}
public String updateInput(){
//根據ID,查詢數據庫,得到User對象
user = new UserManager().findUserById(user.getId());
return "update_input";
}


上述代碼中,new UserManager().findUserById(user.getId());這一行,將從數據庫中查詢相應的記錄,同時轉換為User對象返回。而return “update_input”;將轉向更新顯示頁面。
更新頁面如下:


<form action="test/user.action" method="post">
<input type="hidden" name="method:update">
id:<input type="text" name="id" value="<s:property value="id"/>"> <br/>
username:<input type="text" name="username" value="<s:property value="username"/>"><br/>
password:<input type="text" name="password" value="<s:property value="password"/>"><br/>
age:<input type="text" name="age" value="<s:property value="age"/>"> <br/>
address:<input type="text" name="address" value="<s:property value="address"/>"><br/>
<input type="submit" name="submit" value="更新用戶">
</form> <br/>


上述代碼運行起來之后,你在更新界面上將看不到數據(id屬性有值,其它屬性無顯示)。關鍵的原因是在執行到updateInput之前,user對象(在getMode()方法中創建的對象)被壓到ValueStack中,這時候,UserAction和ValueStack都指向同一個user對象;但緊接着,UserAction中的user被一個新的user對象覆蓋,這時候,UserAction和ValueStack不再指向同一個user對象!ValueStack中是舊的user對象,而UserAction中是新的user對象!我們在JSP中,直接通過username/address等直接訪問,當然是要訪問ValueStack中的舊user對象,所以它們的屬性都是空的(id屬性除外)!
理解上述問題很重要,當你理解了問題,那么問題的解決方法就可以有很多了:
比如,你可以把新對象的屬性拷貝到舊對象上;比如,你可以先把舊對象從ValueStack中移除,然后再把新對象壓入ValueStack等……
在最新的struts2版本中,ModelDrivenInterceptor提供了一個配置參數:refreshModelBeforeResult,只要將它定義為true,上述問題就被解決了!struts2的解決方案就是:先把舊的model對象從ValueStack中移除,然后再把新的model對象壓入ValueStack!
結果:
更新ValueStack中的model對象,先把舊的model對象從ValueStack中移除,然后再把新的model對象壓進ValueStack!
官方解釋:
set to true if you want the model to be refreshed on the value stack after action execution and before result execution. The setting is useful if you want to change the model instance during the action execution phase, like when loading it from the data layer. This will result in getModel() being called at least twice.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM