Struts2中的ModelDriven机制

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!


Struts2即支持属性驱动,也支持模型驱动
属性驱动:在Action中提供与表单字段一一对应的属性,然后一一set赋值
模型驱动:使得表单字段都自动被set到一个JavaBean中,类似于Struts1.XActionForm
采用属性驱动的方式时,是由每个属性来承载表单的字段值,运转在MVC流程里面
采用模型驱动的方式时,是由模型对象来承载所有的属性值,运转在MVC流程里面
若使用模型驱动方式的话,就必须单独提供一个JavaBean
可能与持久层打交道的JavaBean不太一样,因此很可能要对每个对象提供两个JavaBean
比如Struts1的ActionForm,一般来说绝对不会把ActionForm作为JavaBean跟持久层交互
ActionForm仅仅是联系Web和Action的一个桥梁,因此推荐使用属性驱动接收表单字段


com.opensymphony.xwork2.ModelDriven接口源代码中有一段很重要的说明,现抄录如下
ModelDriven Actions provide a model object to be pushed onto the ValueStack in addition
to the Action itself,allowing a FormBean type approach like Struts
翻译:模型驱动的Action。将模型对象以及Action对象都放到ValueStack里面
         允许像Struts一样的FormBean方式
也即:一个Action要想成为模型驱动的话,就必须实现ModelDriven接口
         而我们之前所一直继承的ActionSupport类并没有实现ModelDriven接口
以下是采用模型驱动的Action代码示例

[java]  view plain copy print ?
  1. public class ModelDrivenAction extends ActionSupport implements ModelDriven<User> {  
  2.     private User user = new User();  
  3.     public User getModel() {  
  4.         System.out.println(user);  
  5.         return user;  
  6.     }  
  7.     public String execute() throws Exception {  
  8.         System.out.println("Username is :" + user.getUsername());  
  9.         System.out.println("Password is :" + user.getPassword());  
  10.         return SUCCESS;  
  11.     }  
  12. }  
ModelDrivenAction类的执行流程是:首先调用 getModel()方法 得到User对象
接着根据JavaBean的原则将客户端传过来的属性,一个一个的 set到User对象的属性中
将属性全部set完之后,再执行 execute()方法。对于模型驱动,只要了解这些就足够了


模型驱动的底层实现机制
这里用到了defaultStack拦截器栈中的modelDriven拦截器
它对应com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor类,其API描述如下
public class ModelDrivenInterceptor extends AbstractInterceptor
Watches for ModelDriven actions and adds the action`s model on to the value stack.
翻译:观察模型驱动的Action,并将这个Action的模型【这里指User对象】放到值栈中
Note:The ModelDrivenInterceptor must come before the both StaticParametersInterceptor
and ParametersInterceptor if you want the parameters to be applied to the model.
翻译:若希望将表单提交过来的参数应用到模型里面
         那么ModelDrivenInterceptor拦截器就必须位于StaticParametersInterceptorParametersInterceptor拦截器前面
实际上struts-default.xml已完成这个工作了。可以在defaultStack拦截器栈中查看三者位置
所以对于采用模型驱动的方式的话,在struts.xml中只需要指定模型驱动的类就可以了
其它的都不需要我们手工修改


ModelDrivenInterceptor的部分源代码如下所示

  1. public class ModelDrivenInterceptor extends AbstractInterceptor{  
  2.     public String intercept(ActionInvocation invocation) throws Exception{  
  3.         Object action = invocation.getAction();  
  4.         // 这个action就是当前拦截器准备拦截的Action对象  
  5.         // 我们这里的action就是ModelDrivenAction,它实现了ModelDriven<User>接口  
  6.         // 根据多态性,可认为子类对象就是父类的一个实例,故action属于ModelDriven的实例  
  7.         if(action instanceof ModelDriven){  
  8.             // 将action强制转换为ModelDriven类型,获得ModelDriven的实例  
  9.             ModelDriven modelDriven = (ModelDriven)action;  
  10.             ValueStack stack = invocation.getStack();  
  11.             // 调用getModel(),此时ModelDrivenAction中的getModel()才会被执行,获得User对象  
  12.             Object model = modelDriven.getModel();  
  13.                 if(model != null){  
  14.                     // 获得User对象之后,就把它推入【压入】到值栈中,供后面调用  
  15.                     stack.push(model);  
  16.                 }  
  17.                 //以下略..  


来源:http://blog.csdn.net/li_tengfei/article/details/6098145


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值