上一篇文章介紹了handlermethodargumentresolver的來龍去脈,這篇就要說說自定義handlermethodargumentresolver來解決我們的需求,本文提供了四種解決方案。
需求,有一個teacher類和student類,他們都有屬性name和age:
前端form表單為:
<a href="http://my.oschina.net/pingpangkuangmo/blog/376344#">?</a>
1
2
3
4
5
6
7
<code><form action=</code><code>"/test/two"</code> <code>method=</code><code>"post"</code> <code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"teacher.name"</code> <code>value=</code><code>"張三"</code><code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"teacher.age"</code> <code>value=</code><code>88</code><code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"student.name"</code> <code>value=</code><code>"李四"</code><code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"student.age"</code> <code>value=</code><code>89</code><code>></code>
<code> </code><code><input type=</code><code>"submit"</code> <code>value=</code><code>"送出"</code><code>></code>
<code> </code><code></form></code>
希望背景能這樣接收這樣的參數:
解決方案有很多:
方案一:
建立一個類,融合這兩個類。如
8
9
10
11
12
13
14
15
16
17
<code>public</code> <code>class</code> <code>father {</code>
<code> </code><code>private</code> <code>teacher teacher;</code>
<code> </code><code>private</code> <code>student student;</code>
<code> </code><code>public</code> <code>teacher getteacher() {</code>
<code> </code><code>return</code> <code>teacher;</code>
<code> </code><code>}</code>
<code> </code><code>public</code> <code>void</code> <code>setteacher(teacher teacher) {</code>
<code> </code><code>this</code><code>.teacher = teacher;</code>
<code> </code><code>public</code> <code>student getstudent() {</code>
<code> </code><code>return</code> <code>student;</code>
<code> </code><code>public</code> <code>void</code> <code>setstudent(student student) {</code>
<code> </code><code>this</code><code>.student = student;</code>
<code>}</code>
在背景這樣接收參數:
<code>@requestmapping</code><code>(value=</code><code>"/test/father"</code><code>,method=requestmethod.post)</code>
<code> </code><code>@responsebody</code>
<code> </code><code>public</code> <code>map<string,object> testfather(</code><code>@requestbody</code> <code>father f){</code>
<code> </code><code>//略</code>
<code> </code><code>}</code>
即使用@requestbody來接受這樣的參數。下面還要說說這樣做的兩個問題,你或許可以試猜一下:
使用form表單來進行送出,運作:
問題一:
首先會遇到415 unsupported media type,如下:
我們的form表單預設是以application/x-www-form-urlencoded方式送出的,而@requestbody又采用的是requestresponsebodymethodprocessor這個handlermethodargumentresolver,requestresponsebodymethodprocessor内部的處理原理就是用一系列的httpmessageconverter來進行資料的轉換的。這時候就需要找到支援mediatype類型為application/x-www-form-urlencoded和資料的類型為father的httpmessageconverter,當然就找不到了。我們本意是想讓mappingjackson2httpmessageconverter來處理的,但是它僅僅支援的mediatype類型為:
<code>public</code> <code>mappingjackson2httpmessageconverter() {</code>
<code> </code><code>super</code><code>(</code><code>new</code> <code>mediatype(</code><code>"application"</code><code>,</code><code>"json"</code><code>, default_charset),</code>
<code> </code><code>new</code> <code>mediatype(</code><code>"application"</code><code>,</code><code>"*+json"</code><code>, default_charset));</code>
即application/json或者application/*+json。是以此時就需要我們更改送出的content-type。然而form表單目前的僅僅支援三種content-type即application/x-www-form-urlencoded、multipart/form-data、text/plain。是以我們需要更換成ajax送出,如下:
18
19
<code>function postfather1(){</code>
<code> </code><code>var url=</code><code>'/test/father'</code><code>;</code>
<code> </code><code>var data={</code>
<code> </code><code>'teacher.name'</code><code>:</code><code>'張三'</code> <code>,</code>
<code> </code><code>'teacher.age'</code><code>:</code><code>88</code> <code>,</code>
<code> </code><code>'student.name'</code><code>:</code><code>'李四'</code> <code>,</code>
<code> </code><code>'student.age'</code><code>:</code><code>89</code> <code>,</code>
<code> </code><code>};</code>
<code> </code><code>$.ajax({</code>
<code> </code><code>url:url,</code>
<code> </code><code>type:</code><code>'post'</code><code>,</code>
<code> </code><code>data:json.stringify(data),</code>
<code> </code><code>datatype:</code><code>'json'</code><code>,</code>
<code> </code><code>contenttype:</code><code>"application/json;charset=utf-8"</code><code>,</code>
<code> </code><code>success:function(result){</code>
<code> </code>
<code> </code><code>}</code>
<code> </code><code>});</code>
此時又有一個問題,teacher.name這樣的形式并不能正确解析成father。仍然需要變換格式:
<code>var data={</code>
<code> </code><code>'teacher'</code><code>:{</code>
<code> </code><code>'name'</code><code>:</code><code>'張三'</code><code>,</code>
<code> </code><code>'age'</code><code>:</code><code>88</code>
<code> </code><code>},</code>
<code> </code><code>'student'</code><code>:{</code>
<code> </code><code>'name'</code><code>:</code><code>'李四'</code><code>,</code>
<code> </code><code>'age'</code><code>:</code><code>89</code>
這樣的json形式才能夠被正确解析出來。
是以說方案一有很多的地方要修改,并不是那麼優雅。
方案二:
我們仍然使用form表單送出:
<code><form action=</code><code>"/test/two"</code> <code>method=</code><code>"post"</code><code>></code>
伺服器端的變化為:
<code>@initbinder</code><code>(</code><code>"teacher"</code><code>)</code>
<code> </code><code>public</code> <code>void</code> <code>initbinder1(webdatabinder binder)</code><code>throws</code> <code>exception { </code>
<code> </code><code>binder.setfielddefaultprefix(</code><code>"teacher."</code><code>); </code>
<code> </code>
<code> </code><code>@initbinder</code><code>(</code><code>"student"</code><code>)</code>
<code> </code><code>public</code> <code>void</code> <code>initbinder2(webdatabinder binder)</code><code>throws</code> <code>exception { </code>
<code> </code><code>binder.setfielddefaultprefix(</code><code>"student."</code><code>); </code>
<code>@requestmapping</code><code>(value=</code><code>"/test/two"</code><code>,method=requestmethod.post)</code>
<code> </code><code>public</code> <code>map<string,object> testrequestheader(teacher a,student b){</code>
<code> </code><code>map<string,object> map=</code><code>new</code> <code>hashmap<string,object>();</code>
<code> </code><code>map.put(</code><code>"name"</code><code>,</code><code>"lg"</code><code>);</code>
<code> </code><code>map.put(</code><code>"age"</code><code>,</code><code>23</code><code>);</code>
<code> </code><code>map.put(</code><code>"date"</code><code>,</code><code>new</code> <code>date());</code>
<code> </code><code>return</code> <code>map;</code>
大體上來說就是在解析每個參數時加上字首限制。下面就要看看這個過程的源碼分析:
到底選擇哪個handlermethodargumentresolver來解析我們的參數呢?它最終會選擇servletmodelattributemethodprocessor,看下它的判斷條件:
<code>/**</code>
<code> </code><code>* @return true if the parameter is annotated with {@link modelattribute}</code>
<code> </code><code>* or in default resolution mode also if it is not a simple type.</code>
<code> </code><code>*/</code>
<code> </code><code>@override</code>
<code> </code><code>public</code> <code>boolean</code> <code>supportsparameter(methodparameter parameter) {</code>
<code> </code><code>if</code> <code>(parameter.hasparameterannotation(modelattribute.</code><code>class</code><code>)) {</code>
<code> </code><code>return</code> <code>true</code><code>;</code>
<code> </code><code>else</code> <code>if</code> <code>(</code><code>this</code><code>.annotationnotrequired) {</code>
<code> </code><code>return</code> <code>!beanutils.issimpleproperty(parameter.getparametertype());</code>
<code> </code><code>else</code> <code>{</code>
<code> </code><code>return</code> <code>false</code><code>;</code>
這裡說明了它可以支援兩種情況,一種情況為含有@modelattribute注解的參數,另一種情況就是雖然不含@modelattribute注解,但它并不是簡單類型,如常用的string、date等。你會發現spring會注冊兩個servletmodelattributemethodprocessor,一個annotationnotrequired為false,另一個為true。這主要是因為調用handlermethodargumentresolver的解析順序的原因,如果隻有一個servletmodelattributemethodprocessor,當它判斷參數不含@modelattribute注解,那它就把參數作為非簡單類型來處理,這樣的話,後面很多的handlermethodargumentresolver将無法發揮作用。是以annotationnotrequired=true的servletmodelattributemethodprocessor是在最後才調用的。
然後再具體看看servletmodelattributemethodprocessor的處理過程:
20
21
22
23
24
25
26
27
28
<code>public</code> <code>final</code> <code>object resolveargument(</code>
<code> </code><code>methodparameter parameter, modelandviewcontainer mavcontainer,</code>
<code> </code><code>nativewebrequest request, webdatabinderfactory binderfactory)</code>
<code> </code><code>throws</code> <code>exception {</code>
<code> </code><code>string name = modelfactory.getnameforparameter(parameter);</code>
<code> </code><code>object attribute = (mavcontainer.containsattribute(name)) ?</code>
<code> </code><code>mavcontainer.getmodel().get(name) : createattribute(name, parameter, binderfactory, request);</code>
<code> </code><code>webdatabinder binder = binderfactory.createbinder(request, attribute, name);</code>
<code> </code><code>if</code> <code>(binder.gettarget() !=</code><code>null</code><code>) {</code>
<code> </code><code>bindrequestparameters(binder, request);</code>
<code> </code><code>validateifapplicable(binder, parameter);</code>
<code> </code><code>if</code> <code>(binder.getbindingresult().haserrors()) {</code>
<code> </code><code>if</code> <code>(isbindexceptionrequired(binder, parameter)) {</code>
<code> </code><code>throw</code> <code>new</code> <code>bindexception(binder.getbindingresult());</code>
<code> </code><code>}</code>
<code> </code><code>// add resolved attribute and bindingresult at the end of the model</code>
<code> </code><code>map<string, object> bindingresultmodel = binder.getbindingresult().getmodel();</code>
<code> </code><code>mavcontainer.removeattributes(bindingresultmodel);</code>
<code> </code><code>mavcontainer.addallattributes(bindingresultmodel);</code>
<code> </code><code>return</code> <code>binder.gettarget();</code>
首先就是擷取參數名的過程,string name = modelfactory.getnameforparameter(parameter);具體内容如下:
<code>public</code> <code>static</code> <code>string getnameforparameter(methodparameter parameter) {</code>
<code> </code><code>modelattribute annot = parameter.getparameterannotation(modelattribute.</code><code>class</code><code>);</code>
<code> </code><code>string attrname = (annot !=</code><code>null</code><code>) ? annot.value() :</code><code>null</code><code>;</code>
<code> </code><code>return</code> <code>stringutils.hastext(attrname) ? attrname : conventions.getvariablenameforparameter(parameter);</code>
這裡先嘗試從@modelattribute注解中擷取參數名,若沒有則根據參數類型來擷取參數名
<code>public</code> <code>static</code> <code>string getvariablenameforparameter(methodparameter parameter) {</code>
<code> </code><code>assert.notnull(parameter,</code><code>"methodparameter must not be null"</code><code>);</code>
<code> </code><code>class<?> valueclass;</code>
<code> </code><code>boolean</code> <code>pluralize =</code><code>false</code><code>;</code>
<code> </code><code>if</code> <code>(parameter.getparametertype().isarray()) {</code>
<code> </code><code>valueclass = parameter.getparametertype().getcomponenttype();</code>
<code> </code><code>pluralize =</code><code>true</code><code>;</code>
<code> </code><code>else</code> <code>if</code> <code>(collection.</code><code>class</code><code>.isassignablefrom(parameter.getparametertype())) {</code>
<code> </code><code>valueclass = genericcollectiontyperesolver.getcollectionparametertype(parameter);</code>
<code> </code><code>if</code> <code>(valueclass ==</code><code>null</code><code>) {</code>
<code> </code><code>throw</code> <code>new</code> <code>illegalargumentexception(</code>
<code> </code><code>"cannot generate variable name for non-typed collection parameter type"</code><code>);</code>
<code> </code><code>valueclass = parameter.getparametertype();</code>
<code> </code><code>string name = classutils.getshortnameasproperty(valueclass);</code>
<code> </code><code>return</code> <code>(pluralize ? pluralize(name) : name);</code>
<code>public</code> <code>static</code> <code>string getshortnameasproperty(class<?> clazz) {</code>
<code> </code><code>string shortname = classutils.getshortname(clazz);</code>
<code> </code><code>int</code> <code>dotindex = shortname.lastindexof(</code><code>'.'</code><code>);</code>
<code> </code><code>shortname = (dotindex != -</code><code>1</code> <code>? shortname.substring(dotindex +</code><code>1</code><code>) : shortname);</code>
<code> </code><code>return</code> <code>introspector.decapitalize(shortname);</code>
擷取類的簡單名稱如teacher,然後再進行處理
<code>public</code> <code>static</code> <code>string decapitalize(string name) {</code>
<code> </code><code>if</code> <code>(name ==</code><code>null</code> <code>|| name.length() ==</code><code>0</code><code>) {</code>
<code> </code><code>return</code> <code>name;</code>
<code> </code><code>if</code> <code>(name.length() ></code><code>1</code> <code>&& character.isuppercase(name.charat(</code><code>1</code><code>)) &&</code>
<code> </code><code>character.isuppercase(name.charat(</code><code>0</code><code>))){</code>
<code> </code><code>char</code> <code>chars[] = name.tochararray();</code>
<code> </code><code>chars[</code><code>0</code><code>] = character.tolowercase(chars[</code><code>0</code><code>]);</code>
<code> </code><code>return</code> <code>new</code> <code>string(chars);</code>
有了類的簡單名稱,如果類的簡單名稱第一個和第二個字母都大寫則不進行處理直接傳回類的簡單名稱,否則僅僅将類的第一個大寫變成小寫。就此擷取到了參數名為teacher。
然後就是擷取或者建立我們要綁定的teacher對象。它首先嘗試從要傳回的model中能否找到屬性名為teacher的model,如找不到,就需要去建立一個:
<code>protected</code> <code>final</code> <code>object createattribute(string attributename,</code>
<code> </code><code>methodparameter parameter,</code>
<code> </code><code>webdatabinderfactory binderfactory,</code>
<code> </code><code>nativewebrequest request)</code><code>throws</code> <code>exception {</code>
<code> </code><code>string value = getrequestvalueforattribute(attributename, request);</code>
<code> </code><code>if</code> <code>(value !=</code><code>null</code><code>) {</code>
<code> </code><code>object attribute = createattributefromrequestvalue(value, attributename, parameter, binderfactory, request);</code>
<code> </code><code>if</code> <code>(attribute !=</code><code>null</code><code>) {</code>
<code> </code><code>return</code> <code>attribute;</code>
<code> </code><code>return</code> <code>super</code><code>.createattribute(attributename, parameter, binderfactory, request);</code>
先嘗試從request參數中能否找到teacher這一個參數,找到了就進行綁定和轉換。未找到,就需要自己來執行個體化一個teacher對象,此時并沒有綁定相應的參數值。
有個傳回的目标,然後就是建立webdatabinder實作綁定的過程:
webdatabinder binder = binderfactory.createbinder(request, attribute, name);
<code>public</code> <code>final</code> <code>webdatabinder createbinder(nativewebrequest webrequest, object target, string objectname)</code>
<code> </code><code>webdatabinder databinder = createbinderinstance(target, objectname, webrequest);</code>
<code> </code><code>if</code> <code>(</code><code>this</code><code>.initializer !=</code><code>null</code><code>) {</code>
<code> </code><code>this</code><code>.initializer.initbinder(databinder, webrequest);</code>
<code> </code><code>initbinder(databinder, webrequest);</code>
<code> </code><code>return</code> <code>databinder;</code>
這一個過程,我們之前已經分析過。就是排程執行一些@initbinder方法注冊一些propertyeditor。我們繼續要來看看initbinder(databinder, webrequest);執行了那些@initbinder方法:
<code>public</code> <code>void</code> <code>initbinder(webdatabinder binder, nativewebrequest request)</code><code>throws</code> <code>exception {</code>
<code> </code><code>for</code> <code>(invocablehandlermethod bindermethod :</code><code>this</code><code>.bindermethods) {</code>
<code> </code><code>if</code> <code>(isbindermethodapplicable(bindermethod, binder)) {</code>
<code> </code><code>object returnvalue = bindermethod.invokeforrequest(request,</code><code>null</code><code>, binder);</code>
<code> </code><code>if</code> <code>(returnvalue !=</code><code>null</code><code>) {</code>
<code> </code><code>throw</code> <code>new</code> <code>illegalstateexception(</code><code>"@initbinder methods should return void: "</code> <code>+ bindermethod);</code>
<code>protected</code> <code>boolean</code> <code>isbindermethodapplicable(handlermethod initbindermethod, webdatabinder binder) {</code>
<code> </code><code>initbinder annot = initbindermethod.getmethodannotation(initbinder.</code><code>class</code><code>);</code>
<code> </code><code>collection<string> names = arrays.aslist(annot.value());</code>
<code> </code><code>return</code> <code>(names.size() ==</code><code>0</code> <code>|| names.contains(binder.getobjectname()));</code>
當@initbinder指定了value值的時候,隻有那些value值含有binder.getobjectname()的才會執行,而此時的binder.getobjectname()就是我們辛辛苦苦找出來的參數名teacher。是以本例中@initbinder("teacher")會執行,而@initbinder("student")則不會執行。
之後對四個參數 teacher.name='張三'、teacher.age=88、student.name='李四'、student.age=89 通過字首進行過濾等其他操作實作了參數綁定。此過程不再分析,有興趣的可以繼續研究。
方案三:
使用自定義的handlermethodargumentresolver:
表單送出的内容為:
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"teacher.date"</code> <code>value=</code><code>"2014---09---04 05:23:00"</code><code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"teacher.love"</code> <code>value=</code><code>"乒乓球,籃球"</code><code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"student.date"</code> <code>value=</code><code>"2014---09---05 05:23:00"</code><code>></code>
<code> </code><code><input type=</code><code>"text"</code> <code>name=</code><code>"student.love"</code> <code>value=</code><code>"羽毛球,撞球"</code><code>></code>
其中teacher和student做了相應的修改,加大了資料的複雜性。如下:
<code>public</code> <code>class</code> <code>teacher {</code>
<code> </code><code>private</code> <code>string name;</code>
<code> </code><code>private</code> <code>int</code> <code>age;</code>
<code> </code><code>private</code> <code>date date;</code>
<code> </code><code>private</code> <code>list<string> love;</code>
<code> </code><code>public</code> <code>teacher() {</code>
<code> </code><code>super</code><code>();</code>
<code> </code><code>public</code> <code>teacher(string name,</code><code>int</code> <code>age) {</code>
<code> </code><code>this</code><code>.name = name;</code>
<code> </code><code>this</code><code>.age = age;</code>
<code> </code><code>//get set 省略</code>
請求的的處理函數為:
<code>@initbinder</code>
<code> </code><code>public</code> <code>void</code> <code>initbinder(webdatabinder binder)</code><code>throws</code> <code>exception { </code>
<code> </code><code>dateformat df =</code><code>new</code> <code>simpledateformat(</code><code>"yyyy---mm---dd hh:mm:ss"</code><code>); </code>
<code> </code><code>customdateeditor dateeditor =</code><code>new</code> <code>customdateeditor(df,</code><code>true</code><code>); </code>
<code> </code><code>binder.registercustomeditor(date.</code><code>class</code><code>, dateeditor); </code>
<code> </code><code>public</code> <code>map<string,object> testrequestheader(</code><code>@myform</code> <code>teacher a,</code><code>@myform</code> <code>student b){</code>
經過測試,通過。
自定義了兩個東西,一個就是标簽myform,另一個就是myhandlermethodargumentresolver,并且我們從上一篇文章中知道如何将自定義handlermethodargumentresolver加入handlermethodargumentresolver大軍中。如下:
<code><mvc:annotation-driven ></code>
<code> </code><code><!--其他省略 --></code>
<code> </code><code><mvc:argument-resolvers></code>
<code> </code><code><bean</code><code>class</code><code>=</code><code>"com.lg.mvc.myhandlermethodargumentresolver"</code><code>/></code>
<code> </code><code></mvc:argument-resolvers></code>
<code> </code><code></mvc:annotation-driven></code>
我們來具體分析下這個過程,首先是注解 myform:
<code>package</code> <code>com.lg.annotation;</code>
<code>import</code> <code>java.lang.annotation.documented;</code>
<code>import</code> <code>java.lang.annotation.elementtype;</code>
<code>import</code> <code>java.lang.annotation.retention;</code>
<code>import</code> <code>java.lang.annotation.retentionpolicy;</code>
<code>import</code> <code>java.lang.annotation.target;</code>
<code>@target</code><code>(elementtype.parameter)</code>
<code>@retention</code><code>(retentionpolicy.runtime)</code>
<code>@documented</code>
<code>public</code> <code>@interface</code> <code>myform {</code>
<code> </code><code>string value()</code><code>default</code> <code>""</code><code>;</code>
隻有有一個value屬性,用來指定from表單的中字段的字首,若不指定,我将采取類名首字母小寫的規則來預設字首。如@myform teacher a,預設字首是teacher。
然後就是myhandlermethodargumentresolver,專門用來解析@myform注解的:
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<code>package</code> <code>com.lg.mvc;</code>
<code>import</code> <code>java.lang.reflect.field;</code>
<code>import</code> <code>org.springframework.core.methodparameter;</code>
<code>import</code> <code>org.springframework.util.classutils;</code>
<code>import</code> <code>org.springframework.web.bind.webdatabinder;</code>
<code>import</code> <code>org.springframework.web.bind.support.webdatabinderfactory;</code>
<code>import</code> <code>org.springframework.web.context.request.nativewebrequest;</code>
<code>import</code> <code>org.springframework.web.method.support.handlermethodargumentresolver;</code>
<code>import</code> <code>org.springframework.web.method.support.modelandviewcontainer;</code>
<code>import</code> <code>com.lg.annotation.myform;</code>
<code>public</code> <code>class</code> <code>myhandlermethodargumentresolver</code><code>implements</code> <code>handlermethodargumentresolver{</code>
<code> </code><code>if</code> <code>(parameter.hasparameterannotation(myform.</code><code>class</code><code>)) {</code>
<code> </code><code>return</code> <code>false</code><code>;</code>
<code> </code><code>public</code> <code>object resolveargument(methodparameter parameter,</code>
<code> </code><code>modelandviewcontainer mavcontainer, nativewebrequest webrequest,</code>
<code> </code><code>webdatabinderfactory binderfactory)</code><code>throws</code> <code>exception {</code>
<code> </code><code>if</code> <code>(binderfactory==</code><code>null</code><code>) {</code>
<code> </code><code>return</code> <code>null</code><code>;</code>
<code> </code><code>class<?> targettype=parameter.getparametertype();</code>
<code> </code><code>myform myform=parameter.getparameterannotation(myform.</code><code>class</code><code>);</code>
<code> </code><code>string prefix=getprefix(myform,targettype);</code>
<code> </code><code>object arg=</code><code>null</code><code>;</code>
<code> </code><code>field[] fields=targettype.getdeclaredfields();</code>
<code> </code><code>object target=targettype.newinstance();</code>
<code> </code><code>webdatabinder binder = binderfactory.createbinder(webrequest,</code><code>null</code><code>,prefix);</code>
<code> </code><code>for</code><code>(field field:fields){</code>
<code> </code><code>field.setaccessible(</code><code>true</code><code>);</code>
<code> </code><code>string fieldname=field.getname();</code>
<code> </code><code>class<?> fieldtype=field.gettype();</code>
<code> </code><code>arg = binder.convertifnecessary(webrequest.getparameter(prefix+</code><code>"."</code><code>+fieldname),fieldtype, parameter);</code>
<code> </code><code>field.set(target,arg);</code>
<code> </code><code>return</code> <code>target;</code>
<code> </code><code>private</code> <code>string getprefix(myform myform,class<?> targettype) {</code>
<code> </code><code>string prefix=myform.value();</code>
<code> </code><code>if</code><code>(prefix.equals(</code><code>""</code><code>)){</code>
<code> </code><code>prefix=getdefaultclassname(targettype);</code>
<code> </code><code>return</code> <code>prefix;</code>
<code> </code><code>private</code> <code>string getdefaultclassname(class<?> targettype) {</code>
<code> </code><code>return</code> <code>classutils.getshortnameasproperty(targettype);</code>
其實也挺簡單的。對于supportsparameter方法就是看看有沒有myform注解,若有則處理。
重點就在resolveargument方法上:targettype就是myform所修飾的teacher類或student類,這裡以teacher為例。首先就是調用teacher的無參的構造函數建立一個teacher對象。然後由綁定工廠建立出綁定類,webdatabinder binder = binderfactory.createbinder(webrequest, null,prefix);這一過程已在方案二中分析過了,就是執行那些符合的@initbinder方法,這裡我們傳的值為prefix,即myform的value,若沒指定就是類名的首字母小寫,在這裡就是teacher。也就是說那些@initbinder的value值中含有teacher或者@initbinder沒有指定value值的方法才會被執行。是以我們這裡注冊的日期轉換customdateeditor會被注冊進去。然後就是執行綁定的過程。這個過程就是利用已注冊的propertyeditor和converter來進行field類型的轉換。如下分析
周遊它的field,如string name,fieldtype為string。binder.convertifnecessary(webrequest.getparameter(prefix+"."+fieldname),fieldtype, parameter);這裡就是把teacher.name參數值轉換成fieldtype,都是string,是以就不需要轉換器。對于date date,就是把teacher.date參數的字元串值轉換成date類型,然後就用到了我們注冊的customdateeditor,成功的進行了轉換。對于 list<string> love,就是把teacher.love參數的字元串值轉換成list集合,使用的是spring已經注冊的stringtoarrayconverter,字元串預設是以','分割。
該方案隻能進行簡單類型的轉換(teacher中field都是些簡單類型),還不支援teacher中包含複雜類型如包含其他屬性類。其實也可以做成支援的,就是再稍加改造些,對于field的處理先判斷是否是簡單類型,如address類,若不是則遞歸調用上面的處理過程即對address再次周遊field來實作address中簡單類型的綁定。關鍵就是執行個遞歸調用,其他也沒什麼,有興趣的可以自行研究。本例中的自定義檔案可在後面下載下傳。
方案四:
根據方案二我們其實就可以想到更改下方案二所用到的servletmodelattributemethodprocessor,就可以達到我們想要的結果。即如下:
<code>//重點在這裡在這裡在這裡在這裡在這裡在這裡在這裡</code>
webdatabinder binder = binderfactory.createbinder(request, attribute, name);在建立出webdatabinder後,調用下binder.setfielddefaultprefix(prefix);就可以大功告成了。然而,我們會看到該方法是final,不可覆寫的,我就複制粘貼了一份,出來,建立了一個自定義的myservletmodelattributemethodprocessor以及它對應的注解标簽myservletmodelform,代碼如下:
myservletmodelform内容為:
<code>public</code> <code>@interface</code> <code>myservletmodelform {</code>
myservletmodelattributemethodprocessor的主要内容為:
<code>public</code> <code>boolean</code> <code>supportsparameter(methodparameter parameter) {</code>
<code> </code><code>if</code> <code>(parameter.hasparameterannotation(myservletmodelform.</code><code>class</code><code>)) {</code>
<code> </code><code>mavcontainer.getmodel().get(name) : createattribute(name, parameter, binderfactory, webrequest);</code>
<code> </code><code>webdatabinder binder = binderfactory.createbinder(webrequest, attribute, name);</code>
<code> </code><code>string prefix=getfielddefaultprefix(parameter);</code>
<code> </code><code>if</code><code>(!prefix.equals(</code><code>""</code><code>)){</code>
<code> </code><code>binder.setfielddefaultprefix(prefix+</code><code>"."</code><code>);</code>
<code> </code><code>bindrequestparameters(binder, webrequest);</code>
處理函數代碼為:
<code> </code><code>public</code> <code>map<string,object> testrequestheader(</code><code>@myservletmodelform</code> <code>teacher a,</code><code>@myservletmodelform</code> <code>student b){</code>
并把myservletmodelattributemethodprocessor在xml檔案中進行配置:
<code> </code>
<code> </code><code><mvc:message-converters register-defaults=</code><code>"true"</code><code>></code>
<code> </code><code><bean</code><code>class</code><code>=</code><code>"org.springframework.http.converter.stringhttpmessageconverter"</code><code>> </code>
<code> </code><code><constructor-arg value=</code><code>"utf-8"</code><code>/> </code>
<code> </code><code></bean> </code>
<code> </code><code></mvc:message-converters></code>
<code> </code><code><bean</code><code>class</code><code>=</code><code>"com.lg.mvc.myservletmodelattributemethodprocessor"</code><code>/></code>
經測試,可以。附件中含有這兩個類供下載下傳。