叙述套路:
1.这是个啥东西,它是干嘛用的?
2.我知道它能干啥了,那它咋个用呢?
3.它能跑起来了,但是它是咋跑起来的是啥原理呢?
一、ModelDriven是个啥?他能做什么?
从前端页面到后端的参数传递可以分为属性驱动和对象驱动(我瞎编的词(*^__^*) ),属性驱动的意思就是参数从前端传递到后台之后仍然还是参数,需要我们自己按需组装成对象,对象驱动的意思就是参数从前端传递后台之后就自动被组装为了一个对象,ModelDriven实现的就是对象驱动(专业的说法叫模型驱动,但是这个词一看就很不好懂的样子),它做的事情很简单就是在参数拦截器设置参数之前将一个特定的对象放到值栈的栈顶。因为值栈元素有暴露栈中对象属性的特性,所以我们在前端页面中就可以直接访问这个对象的属性而不是还要用对象名导航了,比如user.name可以直接使用name访问。
二、ModelDriven的使用
使用ModelDriven很简单,只要action实现了一个ModelDriven接口即可,这个接口的代码如下:
1 package com.opensymphony.xwork2; 2 3 4 /** 5 * ModelDriven Actions provide a model object to be pushed onto the ValueStack 6 * in addition to the Action itself, allowing a FormBean type approach like Struts. 7 * 8 * @author Jason Carreira 9 */ 10 public interface ModelDriven<T> { 11 12 /** 13 * Gets the model to be pushed onto the ValueStack instead of the Action itself. 14 * 15 * @return the model 16 */ 17 T getModel(); 18 19 }
这种类似的接口由很多,它们的作用大致如下:
1.类型标识,在别的地方可以使用instanceof来判断一个实例是否实现了某一个接口,这个时候接口起到类型标识的作用。
2.统一动作,使得某一类action可以有统一的动作,比如ModelDriven都可以返回一个对象,Validateable都可以验证数据等等。
需要注意的是ModelDriven是一个泛型接口,所以我们还是尽量在实现的时候就指定类型比较好。
回到正题,来看一个实现了ModelDriven的action:
1 public class LoginAction extends ActionSupport implements ModelDriven<User> { 2 3 private User user; 4 5 public String login() { 6 7 return SUCCESS; 8 } 9 10 @Override 11 public User getModel() { 12 if(user==null) user=new User(); 13 return user; 14 } 15 16 public User getUser() { 17 return user; 18 } 19 20 public void setUser(User user) { 21 this.user = user; 22 } 23 24 }
这个时候在前端页面上就可以直接访问user的username,passwd等等,比如:
<form action="loginAction" method="post"> 用户名:<input type="text" name="username" /><br/> 密 码:<input type="password" name="passwd" /><br/> <input type="submit" value="登录" /> </form>
后台接收到的就是完整的User对象,这只是参数接收的一种方式。
ModelDriven注意事项:执行结果前刷新
如果在execute()等映射到的方法中改变了成员属性user的引用的话,就需要在执行结果前刷新,比如:
public String login() { user=userService.login(user); return SUCCESS; }
登录的功能是根据username和passwd查询一个新的对象,如果为null说明登录失败,否则返回一个user对象里面装满了此用户的各种信息,但是不论如何,成员属性user的引用已经被修改,想一下当这个方法执行完的时候Action中的成员属性user指向的是一个引用,而ValueStack中的对象user指向的是另一个引用,而我们想要的是等下访问栈顶元素就可以访问到刚刚在login()中查出的那个对象呢,很明显,这个结果是做不到这一点的,那么该怎么办呢?就是在执行Result之前将我们的成员变量user的引用(可能已经被修改了)重新放入到栈顶:
需要修改配置文件:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"> <struts> <package name="default" namespace="/" extends="struts-default" abstract="false"> <interceptors> <interceptor-stack name="defaultStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="debugging"/> <interceptor-ref name="scopedModelDriven"/> <!-- 需要传入refreshModelBeforeResult为true,默认为false --> <interceptor-ref name="modelDriven"> <param name="refreshModelBeforeResult">true</param> </interceptor-ref> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo..*,^struts..*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> </interceptor-stack> </interceptors> <action name="loginAction" class="struts_practice_001.LoginAction" method="login"> <result>/success.jsp</result> <result name="input">/login.jsp</result> <result name="error">/error.jsp</result> </action> </package> </struts>
可以使用<s:debug />标签在结果jsp页面查看值栈中的数据已经被刷新为新查询出来的user对象。
总结一下:
1.ModelDriven的实现需要实现一个叫做ModelDriven的接口,然后在getModel()返回一个压入栈顶的对象。
2.如果中间修改了这个Model变量的引用,就需要在执行Result前刷新,所谓刷新就是将最新的Model对象压入栈顶。(需要注意的是如果不是修改的引用的话根本没必要进行刷新,因为只要引用相同的话就可以顺着这个引用找过来,找到的属性还是最新的属性,因为它们只是多个指针指向的堆中的同一块对象内存)
三、ModelDriven的原理
ModelDriven的实现依靠ModelDrivenInterceptor,ModelDrivenInterceptor代码剖析:
1 /** 2 * ModelDrivenInterceptor: 3 * 这个拦截器的作用是将一个对象放到ValueStack的栈顶暴露其属性以便直接访问 4 */ 5 public class ModelDrivenInterceptor extends AbstractInterceptor { 6 7 //默认是不刷新的 8 protected boolean refreshModelBeforeResult = false; 9 10 //配置的时候可以传入refreshModelBeforeResult参数设置是否需要在执行Result前刷新 11 public void setRefreshModelBeforeResult(boolean val) { 12 this.refreshModelBeforeResult = val; 13 } 14 15 @Override 16 public String intercept(ActionInvocation invocation) throws Exception { 17 //取得当前的action实例 18 Object action = invocation.getAction(); 19 20 //判断action是否实现了ModelDriven接口 21 if (action instanceof ModelDriven) { 22 //将action实例转为ModelDriven实例 23 ModelDriven modelDriven = (ModelDriven) action; 24 //获取值栈 25 ValueStack stack = invocation.getStack(); 26 //获取Model 27 Object model = modelDriven.getModel(); 28 //可能是为了避免空指针之类的吧,所以喽,ModelDriven拦截器不会自动创建对象(需注意与action成员变量有区别) 29 if (model != null) { 30 //将Model放到值栈的栈顶 31 stack.push(model); 32 } 33 //判断是否需要在执行结果之前之前刷新 34 if (refreshModelBeforeResult) { 35 //如果需要刷新的,添加一个在执行Result之前要执行的回调对象(对象中有一个回调函数) 36 //我们只需要知道传入的这个对象的beforeResult():void方法要在执行Result之前被调用 37 invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); 38 } 39 } 40 return invocation.invoke(); 41 } 42 43 /** 44 * Refreshes the model instance on the value stack, if it has changed 45 * 如果在 46 */ 47 protected static class RefreshModelBeforeResult implements PreResultListener { 48 //原始Model对象 49 private Object originalModel = null; 50 //当前的Action实例 51 protected ModelDriven action; 52 53 public RefreshModelBeforeResult(ModelDriven action, Object model) { 54 this.originalModel = model; 55 this.action = action; 56 } 57 58 //在执行结果之前调用的回调方法 59 public void beforeResult(ActionInvocation invocation, String resultCode) { 60 //获得值栈 61 ValueStack stack = invocation.getStack(); 62 CompoundRoot root = stack.getRoot(); 63 64 //假设需要刷新 65 boolean needsRefresh = true; 66 //拿到最新的Model对象 67 Object newModel = action.getModel(); 68 69 // Check to see if the new model instance is already on the stack 70 //检查如果这个最新的Model可以在栈中找得到,说明根本就没变,所以就不用刷新了 71 for (Object item : root) { 72 if (item.equals(newModel)) { 73 needsRefresh = false; 74 } 75 } 76 77 // Add the new model on the stack 78 //如果需要将最新的Model对象压入栈顶的话 79 if (needsRefresh) { 80 81 // Clear off the old model instance 82 //首先移除原来的Model对象 83 if (originalModel != null) { 84 root.remove(originalModel); 85 } 86 //然后将最新的Model对象压入栈顶 87 if (newModel != null) { 88 stack.push(newModel); 89 } 90 } 91 } 92 } 93 }