昨天有同学问我问题,他告诉我他的Action中的一个属性明明提供了get/set方法,但是在方法中却获取不到表单中传递过来的值。代码如下(简化后的代码)
1 public class UserAction implements modelDriven<User>(){ 2 private String name; 3 private User model; 4 public void setName(String name){ 5 this.name=name; 6 } 7 public String getName(){ 8 return this.name; 9 } 10 public User getModel(){ 11 this.model=model; 12 } 13 14 public String saveUser() throws Exception{ 15 System.out.println(this.name);//输出null 16 System.out.println(this.model.getName());//正常输出表单中提交的值 17 return "toUserInfoPage"; 18 }; 19 }
经过尝试,我们使用两种方法解决了这个问题,
1.将模型驱动接口直接去掉,这样在目标方法中直接使用System.out.pritln(this.name);可以正常获取到值
2.将拦截器栈改换成paramsPrepareParamsStack拦截器栈也可以解决掉这个问题,这个时候同时使用this.model和通过属性this.name都可以正常获取到值。
这只是尝试,但是到底为什么会这样我们也不清楚,但是肯定是默认拦截器和paramsPrepareParamsStack拦截器的差异造成的。
默认的拦截器栈:defaultStack
1 <!-- A complete stack with all the common interceptors in place. 2 Generally, this stack should be the one you use, though it 3 may do more than you need. Also, the ordering can be 4 switched around (ex: if you wish to have your servlet-related 5 objects applied before prepare() is called, you‘d need to move 6 servletConfig interceptor up. 7 8 This stack also excludes from the normal validation and workflow 9 the method names input, back, and cancel. These typically are 10 associated with requests that should not be validated. 11 --> 12 <interceptor-stack name="defaultStack"> 13 <interceptor-ref name="exception"/> 14 <interceptor-ref name="alias"/> 15 <interceptor-ref name="servletConfig"/> 16 <interceptor-ref name="i18n"/> 17 <interceptor-ref name="prepare"/> 18 <interceptor-ref name="chain"/> 19 <interceptor-ref name="debugging"/> 20 <interceptor-ref name="scopedModelDriven"/> 21 <interceptor-ref name="modelDriven"/> 22 <interceptor-ref name="fileUpload"/> 23 <interceptor-ref name="checkbox"/> 24 <interceptor-ref name="multiselect"/> 25 <interceptor-ref name="staticParams"/> 26 <interceptor-ref name="actionMappingParams"/> 27 <interceptor-ref name="params"> 28 <param name="excludeParams">dojo\..*,^struts\..*</param> 29 </interceptor-ref> 30 <interceptor-ref name="conversionError"/> 31 <interceptor-ref name="validation"> 32 <param name="excludeMethods">input,back,cancel,browse</param> 33 </interceptor-ref> 34 <interceptor-ref name="workflow"> 35 <param name="excludeMethods">input,back,cancel,browse</param> 36 </interceptor-ref> 37 </interceptor-stack>
paramsPrepareParamsStack拦截器栈:
1 <!-- An example of the paramsPrepareParams trick. This stack 2 is exactly the same as the defaultStack, except that it 3 includes one extra interceptor before the prepare interceptor: 4 the params interceptor. 5 6 This is useful for when you wish to apply parameters directly 7 to an object that you wish to load externally (such as a DAO 8 or database or service layer), but can‘t load that object 9 until at least the ID parameter has been loaded. By loading 10 the parameters twice, you can retrieve the object in the 11 prepare() method, allowing the second params interceptor to 12 apply the values on the object. --> 13 <interceptor-stack name="paramsPrepareParamsStack"> 14 <interceptor-ref name="exception"/> 15 <interceptor-ref name="alias"/> 16 <interceptor-ref name="i18n"/> 17 <interceptor-ref name="checkbox"/> 18 <interceptor-ref name="multiselect"/> 19 <interceptor-ref name="params"> 20 <param name="excludeParams">dojo\..*,^struts\..*</param> 21 </interceptor-ref> 22 <interceptor-ref name="servletConfig"/> 23 <interceptor-ref name="prepare"/> 24 <interceptor-ref name="chain"/> 25 <interceptor-ref name="modelDriven"/> 26 <interceptor-ref name="fileUpload"/> 27 <interceptor-ref name="staticParams"/> 28 <interceptor-ref name="actionMappingParams"/> 29 <interceptor-ref name="params"> 30 <param name="excludeParams">dojo\..*,^struts\..*</param> 31 </interceptor-ref> 32 <interceptor-ref name="conversionError"/> 33 <interceptor-ref name="validation"> 34 <param name="excludeMethods">input,back,cancel,browse</param> 35 </interceptor-ref> 36 <interceptor-ref name="workflow"> 37 <param name="excludeMethods">input,back,cancel,browse</param> 38 </interceptor-ref> 39 </interceptor-stack>
由于是paramsPrepareParamsStack拦截器栈解决了问题,所以这里着重看paramsPrepareParamsStack拦截器栈,亮点在前面的注释部分:
This stack is exactly the same as the defaultStack, except that it includes one extra interceptor before the prepare interceptor:the params interceptor.
这句英文意思简单明确,翻译过来大体意思就是paramsPrepareParamsStack拦截器栈和defaultStack拦截器栈相比只有一点不同:paramsPrepareParamsStack拦截器栈在params拦截器之前增加了params拦截器。
简单回顾一下struts2的工作流程,首先当一次Action请求发生的时候,首先在DefaultActionInvocation类中的init方法中会创建Action对象并压栈,接着会执行设置的拦截器栈,执行完毕所有的拦截器之后会执行Action中的目标方法,最后执行结果集。
这里获取参数的过程是在目标方法中完成的,这里获取不到参数是因为在拦截器栈中param拦截器的放置位置不对造成的,如果该拦截器放置到模型驱动拦截器之后则会发生以上问题的冲突;如果将该拦截器放置到模型驱动拦截器之前则不会发生上述的问题(模型驱动拦截器之后也放置相同的一个)
接着看paramsPrepareParamsStack拦截器栈前面的注释描述:
This is useful for when you wish to apply parameters directly to an object that you wish to load externally (such as a DAO or database or service layer), but can‘t load that object until at least the ID parameter has been loaded. By loading the parameters twice, you can retrieve the object in the prepare() method, allowing the second params interceptor to apply the values on the object.
这段英文的意思并不难理解,翻译过来的意思就是:当你想给Action中的一个对象赋值的时候必须得获取该对象的唯一标识id,这样才能通过DAO层或者Service层或者查找数据库获取该对象,所以直到你能够获取到该id的值你都不能加载该对象;通过加载两次param拦截器,你能够在prepare方法中获取该对象,并且能够在第二个param拦截器中将所有的属性值赋值给该对象。
我觉得这段话漏掉了一个重要的拦截器说明,那就是模型驱动拦截器,模型驱动拦截器的作用只有一个:调用Action对象的getModel方法并将获取到的值压栈。然后在param拦截器中对getModel获取到的model对象属性赋值,参数值都是从前端页面中获取到的,比如表单或者Ajax请求等;这样在Action的目标方法中使用Model对象的时候就有值了;既然涉及到了prepare方法的问题了,那么肯定还关系到了PrepareInterceptor拦截器。
通过以上的分析,可以得到以下的工作流程:使用paramsPrepareParamsStack拦截器栈是有一定的时机的,使用paramsPrepareParamsStack拦截器的时候Action一定实现了接口Preparable(反之则不一定),并且有prepare[[Do]MethodName]方法,实现的拦截器是PrepareInterceptor拦截器;为了使得PrepareInterceptor拦截器能够正常工作,ParametersInterceptor拦截器必须提供某些参数值(如id),这个时候就给Action中的属性赋值了;DefaultActionInvocation执行完成PrepareInterceptor拦截器之后(可能做一些赋值的工作,比如为Action中的对象属性赋值(利用ParametersInterceptor拦截器提供的参数值),之前的一个项目就使用这种方法解决了Action中的模型赋值的问题),执行到了ModelDrivenInterceptor拦截器,该拦截器将model对象压栈;接着又有一个ParametersInterceptor拦截器,该拦截器的作用不再是为Action中的属性赋值,而是为model对象中的属性赋值,执行完所有的拦截器之后会执行Action中的目标方法,最后执行结果集。
总的来说,虽然使用paramsPrepareParamsStack拦截器栈解决了之前的那个问题,但是该拦截器栈的设计本意并不是这样,Action中的属性赋值只是其作用中的一个环节,其余的需要使用PrepareInterceptor、ModelDrivenInterceptor以及后面的又一个ParametersInterceptor共同完成。
没想到一个莫名其妙的问题背后竟然有这么多奇妙的东西,看来还需要更加努力才行啊~