ModelDriven
為什么需要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 寫起來都比較簡單。
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 !
引用地址:http://blog.csdn.net/zheng2008hua/article/details/6285737