浅谈Struts2的模型驱动(ModelDrivenInterceptor)和属性封装和struts2数据封装机制
1、模型驱动
@Controller
@Scope("prototype")
public class UserAction extends ActionSupport implements ModelDriven<User>{
private User model = new User();
@Override // implements the ModelDriven
public User getModel(){
return this.model;
}
@Autowired
private UserService userService;
public String addUser(){
userService.add(this.model);
return
SUCCESS;
}
}
ModelDrivenInterceptor源码如下:
为什么实现了ModelDriven之后能在action的方法中直接访问实体类对象user?
因为在到达UserAction的方法之前会先执行struts-default.xml中配置的一些拦截器,包括 ModelDrivenInterceptor、ParametersInterceptor(action的属性封装),进而执行拦截器的拦截方法intercept,看源码可知:
1、先得到了action对象UserAction
2、判断这个action是否实现了ModelDriven
3、若实现了,则将其强转为ModelDriven
4、获得创建ActionProxy之前静态注入的ValueStack的实现类值栈对象
5、调用modelDriven的getModel方法获得实体类对象(本例中为action中创建的User对象)
6、调用值栈的push方法(内部使用getRoot.add(0,obj))将实体类对象放入对象栈的栈顶。
7、在到达ParametersInterceptor的拦截方法时将参数username、password等的值封装对象栈栈顶的model对象中。
struts2的数据机制是:用户提交请求数据,数据是由request.getParameter获得的,因而数据位于request对象的map中。然后创建ActionContext、静态注入ValueStack实例,这时将request对象放入值栈的Map栈中。ParametersInterceptor拦截器又继承自MethodFilterInterceptor,其主要功能是把ActionContext中的请求参数设置到ValueStack中,如果栈顶是当前Action则把请求参数设置到了Action中,如果栈顶是一个model(Action实现了ModelDriven接口)则把参数设置到了model中
为什么ModelDrivenInterceptor和ParametersInterceptor一前一后且顺序不能调换?两种数据封装方式能否同时使用?
在没有使用模型驱动而使用属性封装时
public class UserAction extends ActionSupport{
private String username;
private String password;
public void setUsername(String username){ this.username = username; }
public void setPassword(String password) { this.password = password; }
@Autowired
private UserService userService;
public String add(){
User user = new User();
user.setUsername(username);
user.setPassword(password);
userService.add(user);
return
SUCCESS;
}
}
在没有实现模型驱动时,经过模型驱动拦截器时判断action为ModelDriven的实例失败因此不会有将实体类对象放入
对象栈栈顶这个操作。那么到达ParametersInterceptor的拦截方法时,默认是当前的action对象位于栈顶,request域
中的key/value会被封装到action的属性username、password中,因此到达add方法时这两个参数的值已经被封装好了。
ModelDrivenInterceptor和ParametersInterceptor的顺序为什么不能调换?
倘若调换了两者的顺序,那么到达ParametersInterceptor的拦截方法时,还未经过ModelDrivenInterceptor的拦截
方法将实体类对象放入栈顶,即当前的action对象位于栈顶。而action对象的属性User model是对象类型,request域中的
username,password只能封装到action对象中的String或其他基本类型属性中,因此会导致封装数据失败。
总而言之,模型驱动和属性封装可以同时使用,封装的结果以模型驱动的机制优先,剩余未封装的数据最后封装到action中。
若同时使用两种封装方式:
public class UserAction extends ActionSupport implements ModelDriven<User>{
private User user = new User();
@Override
public User getModel(){
return
this.user;
}
private String username;
private String password;
public void setUsername...
public void setPassword...
public
void add(){ userService.add(user); }
}
分析原则还是根据封装机制为到达ParametersInterceptor的拦截方法时将request域中的key/value封装到位于对象栈
栈顶的对象中。由于到达拦截方法时,ModelDrivenInterceptor将user放入了栈顶,因此action中的user对象封装成功。
而因为参数username、password已经被成功封装到了user对象中,一个参数只会被封装一次,所以action中的username、
password属性值为null,但若页面提交了action中的属性而非模型中的属性则会被封装到action中。
使用ognl表达式封装(本质为属性封装)
public class UserAction extends ActionSupport{
private User user;
public User getUser(){
return
this.user;
}
public void setUser(User user){
this.user
= user;
}
public void add(){ userService.add(user);
}
}
此种封装方式要求页面输入框的name属性值的写法遵循ognl表达式的格式,即对象名.属性名,如user.username
user.password。
此种封装方式的原理是拿参数中的对象名如user到action中找到其get'方法(这里为getUser),通过java的反射机制创建
User类对象,获取User类的属性及setter方法,找到参数中属性名username、password的set方法并注入值。然后调用action
类中的setUser方法为action类中的this.user赋值。