struts2之paramsPrepareParamsStack

2010年5月13日 由 yybean 留言 »

paramsPrepareParamsStack在Struts 2.0中是一个很奇妙的interceptor stack,以至于很多人疑问为何不将其设置为默认的interceptor stack。paramsPrepareParamsStack主要解决了ModelDriven和Preparable的配合问题,从字面上理解来说,这个stack的拦截器调用的顺序为:首先params,然后prepare,接下来modelDriven,最后再params。Struts 2.0的设计上要求modelDriven在params之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在prepare之前运行params拦截器设置相关参数,这个也就是创建paramsPrepareParamsStack的原因。

流程如下:
   1. params拦截器首先给action中的相关参数赋值,如id
   2. prepare拦截器执行prepare方法,prepare方法中会根据参数,如id,去调用业务逻辑,设置model对象
   3. modelDriven拦截器将model对象压入value stack,这里的model对象就是在prepare中创建的
   4. params拦截器再将参数赋值给model对象
   5. action的业务逻辑执行 依据此stack,一个action的代码通常如下:

public class UserAction extends ActionSupport implements ModelDriven, Preparable{

private User user;

private int id;

private UserService service; // user business service 

 

public void setId(int id) {

   this.id = id;

}

/**

* create a new user if none exists, otherwise load the user with the specified id

*/

public void prepare() throws Exception {

   if( id==0 ) {

      user = new User();

   } else {

      user = service.findUserById(id);

   }

}

public Object getModel() {

return user;

}

/**

* create or update the user and then view the created user

*/

public String update() {

   if( id==0 ) {

     service.create(user);

   } else {

     service.update(user);

}

return "redirect";

}

/**

* delete the user and go to a default home page

*/

public String delete() {

service.deleteById(id);

return "home";

}

/**

* show the page allowing the user to view the existing data 

*/

public String view() {

   return "view";

}

/** 

* show the page allowing the user to view the existing data and change the values 

*/

public String edit() {

   return "input";

}

}

 

在上述代码中,edit和view都不需要根据id再为界面准备数据,因为prepare方法已经准备好了model,这些方法很简单。对于update方法,prepare首先会从数据库中加载数据,然后params拦截器会将参数值付给model,在update直接更新就可以,不会出现数据被乱更新的情况。象Hibernate框架,会判断哪些字段更新了,然后进行更新,性能也不会损失。

通过paramsPrepareParamsStack可以让流程更明确,代码更简洁,也更利于大家的交流。

 

上面部分来自:http://blog.csdn.net/Beacher_Ma/archive/2009/09/24/4588186.aspx

这一功能很好很强大,但在与OpenSessionInView结合时,会有一个小陷阱需要防范。见 http://www.javaeye.com/topic/215827

copy 如下

举一个典型的应用场景:

一个Group对象,有id,name等属性

一个User对象,与Group是多对一的关系,User有id,name和group等属性

在修改后保存User时,form表单的input如下:

Html代码

<input type="text" name="user.id" .../>

<input type="text" name="user.name" .../>

<select name="user.group.id">

    <option value=1>group1</option>

    <option value=2>group2</option>

</select>

Action的prepare方法如下:

Java代码

public void prepare() throws Exception {

    if (user != null && user.getId() != null) {

        user = userManager.get(user.getId());

    }

}

某一User的group原来为group1,修改为group2后,点保存,Action执行了updateUser()方法,Hibernate会报错:

Java代码

org.springframework.orm.hibernate3.HibernateSystemException: identifier of an instance of com.xxx.model.Group was altered from 1 to 2; 

nested exception is org.hibernate.HibernateException: identifier of an instance of com.xxx.model.Group was altered from 1 to 2

原来,prepare方法调用后,user对象被重置为hibernate生成的po,user.getGroup()的group对象处于persistent状态,它的id来自于数据库,为1。第二个param拦截器执行了user.getGroup.setId(2)操作,所以抛出了上述异常。

解决办法是,在prepareUpdateUser()方法中执行:

Java代码

public void prepareUpdateUser() throws Exception {

        if (user != null && user.getId() != null) {

            long newGroupId = user.getGroup().getId();

            user = userManager.get(user.getId());

            if (newGroupId != user.getGroup().getId()) {

                Group newGroup = new Group();

                newGroup.setId(newGroupId);

                user.setGroup(newGroup);

            }

        }

}

这样在group修改后手动将与user关联的处于persistent状态的group断开联系。

更简单的方式是直接

Java代码

public void prepareUpdateUser() throws Exception {

        if (client != null && client.getId() != null) {

            client = clientManager.get(client.getId());

            client.setGroup(null);

        }

}

因为第二个param拦截器还会正确地把不管是新的还是老的group id注入进来。

注:该方式只适用于user不对group进行级联更新的情况





发表评论