昨天开发项目的时候一直纠结Struts2使用的对象驱动和模型驱动有什么区别,今天抽空看了网上的资料,结合自己的理解,发现这里面细节还是挺多的...
总的来说,Struts2是使用Ognl表达式将页面传递的值封装到对象或属性的,大致如下图所示。页面携带参数发送请求后,被
struts2的拦截器,拦截器使用ognl找到ValueStack中对应对应的属性进行赋值,完成参数传递。
一共有属性驱动、对象驱动和模型驱动三种情况。虽然从名字的匹配格式上来看属性驱动和模型驱动是一样,但是实际实现原理来讲,属性驱动和对象驱动才是一样,而模型驱动则稍有不用。下面分别讨论。
1.属性驱动
在action中使用属性驱动接收,等价于ongl中的赋值,直接在ValueStack的Root中找到username属性并赋值,所以页面表
单输入框名字要和action中属性名对应一致。
Ognl.setValue("username='akmissxt'",context, root);
下面通过创建自定义拦截器模拟一下struts2的封装过程验证一下猜想,action中添加一个名为"username"的String属性:
public class HelloAction {
private String username;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String execute(){
System.out.println(username);
return "success";
}
}
页面加个表单,不需要创建输入框,提交地址设置一下:
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<form action="${pageContext.request.contextPath }/hello_execute">
<input type="submit">
</form>
</body>
</html>
然后,创建一个自定义拦截器,在拦截器中将{"username":"哈哈哈"}键值对放入ValueStack中:
public class HelloInterceptor extends MethodFilterInterceptor{
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
//前处理
System.out.println("自定义拦截器生效,偷偷放入pinyin的值");
//在ValueStack中添加username
ActionContext.getContext().getValueStack().setValue("username", "哈哈哈");
//放行
invocation.invoke();
//后处理
return null;
}
}
最后,将创建的action和拦截器配置好:
<package name="action" namespace="/" extends="struts-default">
<!-- 配置拦截器 -->
<interceptors >
<!-- 注册拦截器 -->
<interceptor name="myInterceptor" class="com.dengtuzi.action.HelloInterceptor"> </interceptor>
<!-- 自定义拦截器栈 -->
<interceptor-stack name="mystack">
<interceptor-ref name="myInterceptor"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 设置默认拦截器栈 -->
<default-interceptor-ref name="mystack"></default-interceptor-ref>
<action name="hello_*" method="{1}" class="com.dengtuzi.action.HelloAction">
<result name="hello">/hello.jsp</result>
<result name="none"></result>
<result name="success">/hello.jsp</result>
</action>
</package>
根据我们猜想,虽然提交的表单中没有名为"username"的项,但是由于我们的自定义拦截器在默认拦截器链前把username键
值对已经放在ValueStack中,所以struts2可以使用Ognl在ValueStack中找到username封装给action对象。
运行,访问页面,查看控制台,和预期结果一致:
2.对象驱动
在action中设置对象驱动接收,和属性驱动原理一样需要设置set和get方法,前端页面输入框名字格式为“objectname.propertyname”
,等效语句
如下:
Ognl.setValue("user.username='akmissxt'",context, root);
3.模型驱动
使用模型驱动,命名规则和属性驱动一致,Ognl等效语句也一致,但是实现原理不太一样。
Struts2提供了ModelDriven<T>,需要我们实现一个getModel()方法,并且将要接收参数的对象实例化而不需要设置set和get
方法。
为了搞清楚ModelDriven为我们干了什么,打开struts2的拦截器,找到拦截器链中的ModelDrivenInterceptor,里面的过滤逻辑代码
如下:
@Override
public String intercept(ActionInvocation invocation) throws Exception {
Object action = invocation.getAction();//获得action实例
if (action instanceof ModelDriven) {//判断action是否实现ModelDriven接口
ModelDriven modelDriven = (ModelDriven) action;
ValueStack stack = invocation.getStack();
//获得模型实例
Object model = modelDriven.getModel();
if (model != null) {
//如果模型实例不是null,将它压入ValueStack栈顶
stack.push(model);
}
if (refreshModelBeforeResult) {
invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model));
}
}
//继续往下执行拦截器链
return invocation.invoke();
}
我在里面加了注释,这个拦截器大体逻辑就是如果action实现了ModelDriven接口,且接收参数的实例化对象model不为null
的话,将model压入栈顶,这样本来在栈顶位置的action实例就被压下去了,所以这种情况下,Ognl寻找匹配的属性名赋值的
时候就会先从栈顶的model中开始匹配赋值。
下面通过不实现ModelDriven接口自己将接收参数的对象实例压栈模拟一下Struts2的实现过程。
首先创建action,使用Struts2提供的Preparable接口在参数封装前进行压栈操作:
public class HelloAction implements Preparable{
private User user = new User();
public String execute(){
//打印username判断是否封装成功
System.out.println(user.getUsername());
return "success";
}
@Override
//在prepare拦截器中执行
public void prepare() throws Exception {
//获得值栈
ValueStack valueStack = ActionContext.getContext().getValueStack();
//将接收参数对象压栈
valueStack.push(user);
}
}
页面表单中加入对应输入框:
<form action="${pageContext.request.contextPath }/hello_execute">
<input name="username" type="text">
<input type="submit">
</form>
将action加入到配置文件中,运行测试;
和预期结果一致,说明模型驱动和对象驱动的区别在于模型驱动接收的时候Struts2帮我们把接收参数的对象压入ValueStack栈中
这一步操作。
最后附上三种情况的ValueStack内容对比: