昨天有同窗問我問題,他告訴我他的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);能夠正常獲取到值app
2.將攔截器棧改換成paramsPrepareParamsStack攔截器棧也能夠解決掉這個問題,這個時候同時使用this.model和經過屬性this.name均可以正常獲取到值。this
這只是嘗試,可是到底爲何會這樣咱們也不清楚,可是確定是默認攔截器和paramsPrepareParamsStack攔截器的差別形成的。spa
默認的攔截器棧: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攔截器棧:debug
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攔截器棧在prepare攔截器以前增長了params攔截器。code
簡單回顧一下struts2的工做流程,首先當一次Action請求發生的時候,首先在DefaultActionInvocation類中的init方法中會建立Action對象並壓棧,接着會執行設置的攔截器棧,執行完畢全部的攔截器以後會執行Action中的目標方法,最後執行結果集。orm
這裏獲取參數的過程是在目標方法中完成的,這裏獲取不到參數是由於在攔截器棧中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共同完成。
沒想到一個莫名其妙的問題背後居然有這麼多奇妙的東西,看來還須要更加努力才行啊~