javascript
详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] good
目錄
前言
SpringMVC是目前主流的Web MVC框架之一。
如果有同學(xué)對(duì)它不熟悉,那么請參考它的入門blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html
SpringMVC中Controller的方法參數(shù)可以是Integer,Double,自定義對(duì)象,ServletRequest,ServletResponse,ModelAndView等等,非常靈活。本文將分析SpringMVC是如何對(duì)這些參數(shù)進(jìn)行處理的,使讀者能夠處理自定義的一些參數(shù)。
現(xiàn)象
本文使用的demo基于maven。我們先來看一看對(duì)應(yīng)的現(xiàn)象。
@Controller @RequestMapping(value = "/test") public class TestController {@RequestMapping("/testRb")@ResponseBodypublic Employee testRb(@RequestBody Employee e) {return e;}@RequestMapping("/testCustomObj")@ResponseBodypublic Employee testCustomObj(Employee e) {return e;}@RequestMapping("/testCustomObjWithRp")@ResponseBodypublic Employee testCustomObjWithRp(@RequestParam Employee e) {return e;}@RequestMapping("/testDate")@ResponseBodypublic Date testDate(Date date) {return date;}}首先這是一個(gè)Controller,有4個(gè)方法。他們對(duì)應(yīng)的參數(shù)分別是帶有@RequestBody的自定義對(duì)象、自定義對(duì)象、帶有@RequestParam的自定義對(duì)象、日期對(duì)象。
接下來我們一個(gè)一個(gè)方法進(jìn)行訪問看對(duì)應(yīng)的現(xiàn)象是如何的。
首先第一個(gè)testRb:
第二個(gè)testCustomObj:
第三個(gè)testCustomObjWithRp:
第四個(gè)testDate:
?
為何返回的Employee對(duì)象會(huì)被自動(dòng)解析為xml,請看樓主的另一篇博客:戳我
為何Employee參數(shù)會(huì)被解析,帶有@RequestParam的Employee參數(shù)不會(huì)被解析,甚至報(bào)錯(cuò)?
為何日期類型不能被解析?
SpringMVC到底是如何處理這些方法的參數(shù)的?
@RequestBody、@RequestParam這兩個(gè)注解有什么區(qū)別?
帶著這幾個(gè)問題。我們開始進(jìn)行分析。
源碼分析
本文所分析的源碼是Spring版本4.0.2
在分析源碼之前,首先讓我們來看下SpringMVC中兩個(gè)重要的接口。
兩個(gè)接口分別對(duì)應(yīng)請求方法參數(shù)的處理、響應(yīng)返回值的處理,分別是HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler,這兩個(gè)接口都是Spring3.1版本之后加入的。
SpringMVC處理請求大致是這樣的:
首先被DispatcherServlet截獲,DispatcherServlet通過handlerMapping獲得HandlerExecutionChain,然后獲得HandlerAdapter。
HandlerAdapter在內(nèi)部對(duì)于每個(gè)請求,都會(huì)實(shí)例化一個(gè)ServletInvocableHandlerMethod進(jìn)行處理,ServletInvocableHandlerMethod在進(jìn)行處理的時(shí)候,會(huì)分兩部分別對(duì)請求跟響應(yīng)進(jìn)行處理。
之后HandlerAdapter得到ModelAndView,然后做相應(yīng)的處理。
本文將重點(diǎn)介紹ServletInvocableHandlerMethod對(duì)請求以及響應(yīng)的處理。
1. 處理請求的時(shí)候,會(huì)根據(jù)ServletInvocableHandlerMethod的屬性argumentResolvers(這個(gè)屬性是它的父類InvocableHandlerMethod中定義的)進(jìn)行處理,其中argumentResolvers屬性是一個(gè)HandlerMethodArgumentResolverComposite類(這里使用了組合模式的一種變形),這個(gè)類是實(shí)現(xiàn)了HandlerMethodArgumentResolver接口的類,里面有各種實(shí)現(xiàn)了HandlerMethodArgumentResolver的List集合。
2. 處理響應(yīng)的時(shí)候,會(huì)根據(jù)ServletInvocableHandlerMethod的屬性returnValueHandlers(自身屬性)進(jìn)行處理,returnValueHandlers屬性是一個(gè)HandlerMethodReturnValueHandlerComposite類(這里使用了組合模式的一種變形),這個(gè)類是實(shí)現(xiàn)了HandlerMethodReturnValueHandler接口的類,里面有各種實(shí)現(xiàn)了HandlerMethodReturnValueHandler的List集合。
ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers這兩個(gè)屬性都是在ServletInvocableHandlerMethod進(jìn)行實(shí)例化的時(shí)候被賦值的(使用RequestMappingHandlerAdapter的屬性進(jìn)行賦值)。
RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers這兩個(gè)屬性是在RequestMappingHandlerAdapter進(jìn)行實(shí)例化的時(shí)候被Spring容器注入的。
其中默認(rèn)的ArgumentResolvers:
默認(rèn)的returnValueHandlers:
?
我們在json、xml自動(dòng)轉(zhuǎn)換那篇文章中已經(jīng)了解,使用@ResponseBody注解的話最終返回值會(huì)被RequestResponseBodyMethodProcessor這個(gè)HandlerMethodReturnValueHandler實(shí)現(xiàn)類處理。
我們通過源碼發(fā)現(xiàn),RequestResponseBodyMethodProcessor這個(gè)類其實(shí)同時(shí)實(shí)現(xiàn)了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver這兩個(gè)接口。
RequestResponseBodyMethodProcessor支持的請求類型是Controller方法參數(shù)中帶有@RequestBody注解,支持的響應(yīng)類型是Controller方法帶有@ResponseBody注解。
RequestResponseBodyMethodProcessor響應(yīng)的具體處理是使用消息轉(zhuǎn)換器。
處理請求的時(shí)候使用內(nèi)部的readWithMessageConverters方法。
然后會(huì)執(zhí)行父類(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。
?
下面來我們來看看常用的HandlerMethodArgumentResolver實(shí)現(xiàn)類(本文粗略講下,有興趣的讀者可自行研究)。
1. RequestParamMethodArgumentResolver
支持帶有@RequestParam注解的參數(shù)或帶有MultipartFile類型的參數(shù)
2. RequestParamMapMethodArgumentResolver
支持帶有@RequestParam注解的參數(shù) && @RequestParam注解的屬性value存在 && 參數(shù)類型是實(shí)現(xiàn)Map接口的屬性
3. PathVariableMethodArgumentResolver
支持帶有@PathVariable注解的參數(shù) 且如果參數(shù)實(shí)現(xiàn)了Map接口,@PathVariable注解需帶有value屬性
4. MatrixVariableMethodArgumentResolver
支持帶有@MatrixVariable注解的參數(shù) 且如果參數(shù)實(shí)現(xiàn)了Map接口,@MatrixVariable注解需帶有value屬性
5. RequestResponseBodyMethodProcessor
本文已分析過
6. ServletRequestMethodArgumentResolver
參數(shù)類型是實(shí)現(xiàn)或繼承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod這些類。
(這就是為何我們在Controller中的方法里添加一個(gè)HttpServletRequest參數(shù),Spring會(huì)為我們自動(dòng)獲得HttpServletRequest對(duì)象的原因)
7. ServletResponseMethodArgumentResolver
參數(shù)類型是實(shí)現(xiàn)或繼承或是ServletResponse、OutputStream、Writer這些類
8. RedirectAttributesMethodArgumentResolver
參數(shù)是實(shí)現(xiàn)了RedirectAttributes接口的類
9. HttpEntityMethodProcessor
參數(shù)類型是HttpEntity
從名字我們也看的出來, 以Resolver結(jié)尾的是實(shí)現(xiàn)了HandlerMethodArgumentResolver接口的類,以Processor結(jié)尾的是實(shí)現(xiàn)了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的類。
?
下面來我們來看看常用的HandlerMethodReturnValueHandler實(shí)現(xiàn)類。
1. ModelAndViewMethodReturnValueHandler
返回值類型是ModelAndView或其子類
2. ModelMethodProcessor
返回值類型是Model或其子類
3. ViewMethodReturnValueHandler
返回值類型是View或其子類
4. HttpHeadersReturnValueHandler
返回值類型是HttpHeaders或其子類
5. ModelAttributeMethodProcessor
返回值有@ModelAttribute注解
6. ViewNameMethodReturnValueHandler
返回值是void或String
其余沒講過的讀者可自行查看源碼。
?
下面開始解釋為何本文開頭出現(xiàn)那些現(xiàn)象的原因:
1. 第一個(gè)方法testRb以及地址 http://localhost:8888/SpringMVCDemo/test/testRb?name=1&age=3
這個(gè)方法的參數(shù)使用了@RequestBody,之前已經(jīng)分析過,被RequestResponseBodyMethodProcessor進(jìn)行處理。之后根據(jù)http請求頭部的contentType然后選擇合適的消息轉(zhuǎn)換器進(jìn)行讀取。
很明顯,我們的消息轉(zhuǎn)換器只有默認(rèn)的那些跟部分json以及xml轉(zhuǎn)換器,且傳遞的參數(shù)name=1&age=3,傳遞的頭部中沒有content-type,默認(rèn)使用了application/octet-stream,因此觸發(fā)了HttpMediaTypeNotSupportedException異常
解放方案: 我們將傳遞數(shù)據(jù)改成json,同時(shí)http請求的Content-Type改成application/json即可。
完美解決。
2. testCustomObj方法以及地址 http://localhost:8888/SpringMVCDemo/test/testCustomObj?name=1&age=3
這個(gè)請求會(huì)找到ServletModelAttributeMethodProcessor這個(gè)resolver。默認(rèn)的resolver中有兩個(gè)ServletModelAttributeMethodProcessor,只不過實(shí)例化的時(shí)候?qū)傩詀nnotationNotRequired一個(gè)為true,1個(gè)為false。這個(gè)ServletModelAttributeMethodProcessor處理參數(shù)支持@ModelAttribute注解,annotationNotRequired屬性為true的話,參數(shù)不是簡單類型就通過,因此選擇了ServletModelAttributeMethodProcessor,最終通過DataBinder實(shí)例化Employee對(duì)象,并寫入對(duì)應(yīng)的屬性。
3. testCustomObjWithRp方法以及地址 http://localhost:8888/SpringMVCDemo/test/testCustomObjWithRp?name=1&age=3
這個(gè)請求會(huì)找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在處理參數(shù)的時(shí)候使用request.getParameter(參數(shù)名)即request.getParameter("e")得到,很明顯我們的參數(shù)傳的是name=1&age=3。因此得到null,RequestParamMethodArgumentResolver處理missing value會(huì)觸發(fā)MissingServletRequestParameterException異常。 [粗略講下,有興趣的讀者請自行查看源碼]
解決方案:去掉@RequestParam注解,讓ServletModelAttributeMethodProcessor來處理。
4. testDate方法以及地址 http://localhost:8888/SpringMVCDemo/test/testDate?date=2014-05-15
這個(gè)請求會(huì)找到RequestParamMethodArgumentResolver。因?yàn)檫@個(gè)方法與第二個(gè)方法一樣,有兩個(gè)RequestParamMethodArgumentResolver,屬性u(píng)seDefaultResolution不同。RequestParamMethodArgumentResolver支持簡單類型,ServletModelAttributeMethodProcessor是支持非簡單類型。最終步驟跟第三個(gè)方法一樣,我們的參數(shù)名是date,于是通過request.getParameter("date")找到date字符串(這里參數(shù)名如果不是date,那么最終頁面是空白的,因?yàn)闆]有@RequestParam注解,參數(shù)不是必須的,RequestParamMethodArgumentResolver處理null值返回null)。最后通過DataBinder找到合適的屬性編輯器進(jìn)行類型轉(zhuǎn)換。最終找到j(luò)ava.util.Date對(duì)象的構(gòu)造函數(shù) public Date(String s),由于我們傳遞的格式不是標(biāo)準(zhǔn)的UTC時(shí)間格式,因此最終觸發(fā)了IllegalArgumentException異常。
解決方案:
1. 傳遞參數(shù)的格式修改成標(biāo)準(zhǔn)的UTC時(shí)間格式:http://localhost:8888/SpringMVCDemo/test/testDate?date=Sat, 17 May 2014 16:30:00 GMT
2.在Controller中加入自定義屬性編輯器。
@InitBinder public void initBinder(WebDataBinder binder) {SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false)); }這個(gè)@InitBinder注解在實(shí)例化ServletInvocableHandlerMethod的時(shí)候被注入到WebDataBinderFactory中的,而WebDataBinderFactory是ServletInvocableHandlerMethod的一個(gè)屬性。在RequestMappingHandlerAdapter源碼的803行g(shù)etDataBinderFactory就是得到的WebDataBinderFactory。
之后RequestParamMethodArgumentResolver通過WebDataBinderFactory創(chuàng)建的WebDataBinder里的自定義屬性編輯器找到合適的屬性編輯器(我們自定義的屬性編輯器是用CustomDateEditor處理Date對(duì)象,而testDate的參數(shù)剛好是Date),最終CustomDateEditor把這個(gè)String對(duì)象轉(zhuǎn)換成Date對(duì)象。
編寫自定義的HandlerMethodArgumentResolver
通過前面的分析,我們明白了SpringMVC處理Controller中的方法的參數(shù)流程。
現(xiàn)在,如果方法中有兩個(gè)參數(shù),且都是自定義類參數(shù),那該如何處理呢?
很明顯,要處理這個(gè)只能自己實(shí)現(xiàn)一個(gè)實(shí)現(xiàn)HandlerMethodArgumentResolver的類。
先定義1個(gè)注解FormObj:
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface FormObj {//參數(shù)別名String value() default "";//是否展示, 默認(rèn)展示boolean show() default true; }?
然后是HandlerMethodArgumentResolver:
public class FormObjArgumentResolver implements HandlerMethodArgumentResolver {@Overridepublic boolean supportsParameter(MethodParameter parameter) {return parameter.hasParameterAnnotation(FormObj.class);}@Overridepublic Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {FormObj formObj = parameter.getParameterAnnotation(FormObj.class);String alias = getAlias(formObj, parameter);//拿到obj, 先從ModelAndViewContainer中拿,若沒有則new1個(gè)參數(shù)類型的實(shí)例Object obj = (mavContainer.containsAttribute(alias)) ?mavContainer.getModel().get(alias) : createAttribute(alias, parameter, binderFactory, webRequest);//獲得WebDataBinder,這里的具體WebDataBinder是ExtendedServletRequestDataBinderWebDataBinder binder = binderFactory.createBinder(webRequest, obj, alias);Object target = binder.getTarget();if(target != null) {//綁定參數(shù) bindParameters(webRequest, binder, alias);//JSR303 驗(yàn)證 validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors()) {if (isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}}if(formObj.show()) {mavContainer.addAttribute(alias, target);}return target;}private Object createAttribute(String alias, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) {return BeanUtils.instantiateClass(parameter.getParameterType());}private void bindParameters(NativeWebRequest request, WebDataBinder binder, String alias) {ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);MockHttpServletRequest newRequest = new MockHttpServletRequest();Enumeration<String> enu = servletRequest.getParameterNames();while(enu.hasMoreElements()) {String paramName = enu.nextElement();if(paramName.startsWith(alias)) {newRequest.setParameter(paramName.substring(alias.length()+1), request.getParameter(paramName));}}((ExtendedServletRequestDataBinder)binder).bind(newRequest);}protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {Annotation[] annotations = parameter.getParameterAnnotations();for (Annotation annot : annotations) {if (annot.annotationType().getSimpleName().startsWith("Valid")) {Object hints = AnnotationUtils.getValue(annot);binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});break;}}}protected boolean isBindExceptionRequired(WebDataBinder binder, MethodParameter parameter) {int i = parameter.getParameterIndex();Class<?>[] paramTypes = parameter.getMethod().getParameterTypes();boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1]));return !hasBindingResult;}private String getAlias(FormObj formObj, MethodParameter parameter) {//得到FormObj的屬性value,也就是對(duì)象參數(shù)的簡稱String alias = formObj.value();if(alias == null || StringUtils.isBlank(alias)) {//如果簡稱為空,取對(duì)象簡稱的首字母小寫開頭String simpleName = parameter.getParameterType().getSimpleName();alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);}return alias;}}?
對(duì)應(yīng)Controller:
@Controller @RequestMapping(value = "/foc") public class FormObjController {@RequestMapping("/test1")public String test1(@FormObj Dept dept, @FormObj Employee emp) {return "index";}@RequestMapping("/test2")public String test2(@FormObj("d") Dept dept, @FormObj("e") Employee emp) {return "index";}@RequestMapping("/test3")public String test3(@FormObj(value = "d", show = false) Dept dept, @FormObj("e") Employee emp) {return "index";}}結(jié)果如下:
總結(jié)
寫了這么多,主要還是鞏固一下自己對(duì)SpringMVC對(duì)請求及響應(yīng)的處理做一個(gè)細(xì)節(jié)的總結(jié)吧,不知道大家有沒有清楚這個(gè)過程。
想熟悉這部分內(nèi)容最主要的還是要熟悉HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler這兩個(gè)接口以及屬性編輯器、數(shù)據(jù)綁定機(jī)制。
本文難免有錯(cuò)誤,希望讀者能指出來。
參考資料
http://www.iteye.com/topic/1127676
http://jinnianshilongnian.iteye.com/blog/1717180
[http://www.tuicool.com/articles/F7byQn
http://www.2cto.com/kf/201405/301660.html]
?
總結(jié)
以上是生活随笔為你收集整理的详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析] good的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: Linux用ICMP协议实现简单Ping
- 下一篇: java第一季2.2
