1. 程式人生 > >struts中ModelDriven()介面 Struts2中的ModelDriven機制及其運用、refreshModelBeforeResult屬性解決的問題

struts中ModelDriven()介面 Struts2中的ModelDriven機制及其運用、refreshModelBeforeResult屬性解決的問題

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.


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.