詳解SpringMVC中Controller的方法中參數的工作原理[附帶源碼分析] good

目錄

前言

SpringMVC是目前主流的Web MVC框架之一。

如果有同學對它不熟悉,那么請參考它的入門blog:http://www.cnblogs.com/fangjian0423/p/springMVC-introduction.html

SpringMVC中Controller的方法參數可以是Integer,Double,自定義對象,ServletRequest,ServletResponse,ModelAndView等等,非常靈活。本文將分析SpringMVC是如何對這些參數進行處理的,使讀者能夠處理自定義的一些參數。

現象

本文使用的demo基于maven。我們先來看一看對應的現象。

@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;}}

首先這是一個Controller,有4個方法。他們對應的參數分別是帶有@RequestBody的自定義對象、自定義對象、帶有@RequestParam的自定義對象、日期對象。

接下來我們一個一個方法進行訪問看對應的現象是如何的。

首先第一個testRb:

第二個testCustomObj:

第三個testCustomObjWithRp:

第四個testDate:

?

為何返回的Employee對象會被自動解析為xml,請看樓主的另一篇博客:戳我

為何Employee參數會被解析,帶有@RequestParam的Employee參數不會被解析,甚至報錯?

為何日期類型不能被解析?

SpringMVC到底是如何處理這些方法的參數的?

@RequestBody、@RequestParam這兩個注解有什么區別?

帶著這幾個問題。我們開始進行分析。

源碼分析

本文所分析的源碼是Spring版本4.0.2

在分析源碼之前,首先讓我們來看下SpringMVC中兩個重要的接口。

兩個接口分別對應請求方法參數的處理、響應返回值的處理,分別是HandlerMethodArgumentResolverHandlerMethodReturnValueHandler,這兩個接口都是Spring3.1版本之后加入的。

SpringMVC處理請求大致是這樣的:

首先被DispatcherServlet截獲,DispatcherServlet通過handlerMapping獲得HandlerExecutionChain,然后獲得HandlerAdapter。

HandlerAdapter在內部對于每個請求,都會實例化一個ServletInvocableHandlerMethod進行處理,ServletInvocableHandlerMethod在進行處理的時候,會分兩部分別對請求跟響應進行處理

之后HandlerAdapter得到ModelAndView,然后做相應的處理。

本文將重點介紹ServletInvocableHandlerMethod對請求以及響應的處理。

1. 處理請求的時候,會根據ServletInvocableHandlerMethod的屬性argumentResolvers(這個屬性是它的父類InvocableHandlerMethod中定義的)進行處理,其中argumentResolvers屬性是一個HandlerMethodArgumentResolverComposite類(這里使用了組合模式的一種變形),這個類是實現了HandlerMethodArgumentResolver接口的類,里面有各種實現了HandlerMethodArgumentResolver的List集合。

2. 處理響應的時候,會根據ServletInvocableHandlerMethod的屬性returnValueHandlers(自身屬性)進行處理,returnValueHandlers屬性是一個HandlerMethodReturnValueHandlerComposite類(這里使用了組合模式的一種變形),這個類是實現了HandlerMethodReturnValueHandler接口的類,里面有各種實現了HandlerMethodReturnValueHandler的List集合。

ServletInvocableHandlerMethod的returnValueHandlers和argumentResolvers這兩個屬性都是在ServletInvocableHandlerMethod進行實例化的時候被賦值的(使用RequestMappingHandlerAdapter的屬性進行賦值)。

RequestMappingHandlerAdapter的argumentResolvers和returnValueHandlers這兩個屬性是在RequestMappingHandlerAdapter進行實例化的時候被Spring容器注入的。

其中默認的ArgumentResolvers:

默認的returnValueHandlers:

?

我們在json、xml自動轉換那篇文章中已經了解,使用@ResponseBody注解的話最終返回值會被RequestResponseBodyMethodProcessor這個HandlerMethodReturnValueHandler實現類處理。

我們通過源碼發現,RequestResponseBodyMethodProcessor這個類其實同時實現了HandlerMethodReturnValueHandler和HandlerMethodArgumentResolver這兩個接口。

RequestResponseBodyMethodProcessor支持的請求類型是Controller方法參數中帶有@RequestBody注解,支持的響應類型是Controller方法帶有@ResponseBody注解。

RequestResponseBodyMethodProcessor響應的具體處理是使用消息轉換器。

處理請求的時候使用內部的readWithMessageConverters方法。

然后會執行父類(AbstractMessageConverterMethodArgumentResolver)的readWithMessageConverters方法。

?

下面來我們來看看常用的HandlerMethodArgumentResolver實現類(本文粗略講下,有興趣的讀者可自行研究)。

1. RequestParamMethodArgumentResolver

支持帶有@RequestParam注解的參數或帶有MultipartFile類型的參數

2. RequestParamMapMethodArgumentResolver

支持帶有@RequestParam注解的參數 && @RequestParam注解的屬性value存在 && 參數類型是實現Map接口的屬性

3. PathVariableMethodArgumentResolver

支持帶有@PathVariable注解的參數 且如果參數實現了Map接口,@PathVariable注解需帶有value屬性

4. MatrixVariableMethodArgumentResolver

支持帶有@MatrixVariable注解的參數 且如果參數實現了Map接口,@MatrixVariable注解需帶有value屬性

5. RequestResponseBodyMethodProcessor

本文已分析過

6. ServletRequestMethodArgumentResolver

參數類型是實現或繼承或是WebRequest、ServletRequest、MultipartRequest、HttpSession、Principal、Locale、TimeZone、InputStream、Reader、HttpMethod這些類。

(這就是為何我們在Controller中的方法里添加一個HttpServletRequest參數,Spring會為我們自動獲得HttpServletRequest對象的原因)

7. ServletResponseMethodArgumentResolver

參數類型是實現或繼承或是ServletResponse、OutputStream、Writer這些類

8. RedirectAttributesMethodArgumentResolver

參數是實現了RedirectAttributes接口的類

9. HttpEntityMethodProcessor

參數類型是HttpEntity

從名字我們也看的出來, 以Resolver結尾的是實現了HandlerMethodArgumentResolver接口的類,以Processor結尾的是實現了HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler的類。

?

下面來我們來看看常用的HandlerMethodReturnValueHandler實現類。

1. ModelAndViewMethodReturnValueHandler

返回值類型是ModelAndView或其子類

2. ModelMethodProcessor

返回值類型是Model或其子類

3. ViewMethodReturnValueHandler

返回值類型是View或其子類

4. HttpHeadersReturnValueHandler

返回值類型是HttpHeaders或其子類

5. ModelAttributeMethodProcessor

返回值有@ModelAttribute注解

6. ViewNameMethodReturnValueHandler

返回值是void或String

其余沒講過的讀者可自行查看源碼。

?

下面開始解釋為何本文開頭出現那些現象的原因:

1. 第一個方法testRb以及地址 http://localhost:8888/SpringMVCDemo/test/testRb?name=1&age=3

  這個方法的參數使用了@RequestBody,之前已經分析過,被RequestResponseBodyMethodProcessor進行處理。之后根據http請求頭部的contentType然后選擇合適的消息轉換器進行讀取。

  很明顯,我們的消息轉換器只有默認的那些跟部分json以及xml轉換器,且傳遞的參數name=1&age=3,傳遞的頭部中沒有content-type,默認使用了application/octet-stream,因此觸發了HttpMediaTypeNotSupportedException異常

  解放方案: 我們將傳遞數據改成json,同時http請求的Content-Type改成application/json即可。

完美解決。

2. testCustomObj方法以及地址 http://localhost:8888/SpringMVCDemo/test/testCustomObj?name=1&age=3

這個請求會找到ServletModelAttributeMethodProcessor這個resolver。默認的resolver中有兩個ServletModelAttributeMethodProcessor,只不過實例化的時候屬性annotationNotRequired一個為true,1個為false。這個ServletModelAttributeMethodProcessor處理參數支持@ModelAttribute注解,annotationNotRequired屬性為true的話,參數不是簡單類型就通過,因此選擇了ServletModelAttributeMethodProcessor,最終通過DataBinder實例化Employee對象,并寫入對應的屬性。

3. testCustomObjWithRp方法以及地址 http://localhost:8888/SpringMVCDemo/test/testCustomObjWithRp?name=1&age=3

這個請求會找到RequestParamMethodArgumentResolver(使用了@RequestParam注解)。RequestParamMethodArgumentResolver在處理參數的時候使用request.getParameter(參數名)即request.getParameter("e")得到,很明顯我們的參數傳的是name=1&age=3。因此得到null,RequestParamMethodArgumentResolver處理missing value會觸發MissingServletRequestParameterException異常。 [粗略講下,有興趣的讀者請自行查看源碼]

解決方案:去掉@RequestParam注解,讓ServletModelAttributeMethodProcessor來處理。

4. testDate方法以及地址 http://localhost:8888/SpringMVCDemo/test/testDate?date=2014-05-15

這個請求會找到RequestParamMethodArgumentResolver。因為這個方法與第二個方法一樣,有兩個RequestParamMethodArgumentResolver,屬性useDefaultResolution不同。RequestParamMethodArgumentResolver支持簡單類型,ServletModelAttributeMethodProcessor是支持非簡單類型。最終步驟跟第三個方法一樣,我們的參數名是date,于是通過request.getParameter("date")找到date字符串(這里參數名如果不是date,那么最終頁面是空白的,因為沒有@RequestParam注解,參數不是必須的,RequestParamMethodArgumentResolver處理null值返回null)。最后通過DataBinder找到合適的屬性編輯器進行類型轉換。最終找到java.util.Date對象的構造函數 public Date(String s),由于我們傳遞的格式不是標準的UTC時間格式,因此最終觸發了IllegalArgumentException異常。

解決方案:

1. 傳遞參數的格式修改成標準的UTC時間格式: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));
}

這個@InitBinder注解在實例化ServletInvocableHandlerMethod的時候被注入到WebDataBinderFactory中的,而WebDataBinderFactory是ServletInvocableHandlerMethod的一個屬性。在RequestMappingHandlerAdapter源碼的803行getDataBinderFactory就是得到的WebDataBinderFactory。

之后RequestParamMethodArgumentResolver通過WebDataBinderFactory創建的WebDataBinder里的自定義屬性編輯器找到合適的屬性編輯器(我們自定義的屬性編輯器是用CustomDateEditor處理Date對象,而testDate的參數剛好是Date),最終CustomDateEditor把這個String對象轉換成Date對象。

編寫自定義的HandlerMethodArgumentResolver

通過前面的分析,我們明白了SpringMVC處理Controller中的方法的參數流程。

現在,如果方法中有兩個參數,且都是自定義類參數,那該如何處理呢?

很明顯,要處理這個只能自己實現一個實現HandlerMethodArgumentResolver的類。

先定義1個注解FormObj:

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface FormObj {//參數別名String value() default "";//是否展示, 默認展示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個參數類型的實例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) {//綁定參數
            bindParameters(webRequest, binder, alias);//JSR303 驗證
            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,也就是對象參數的簡稱String alias = formObj.value();if(alias == null || StringUtils.isBlank(alias)) {//如果簡稱為空,取對象簡稱的首字母小寫開頭String simpleName = parameter.getParameterType().getSimpleName();alias = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);}return alias;}}

?

 

對應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";}}
 

結果如下:

總結

寫了這么多,主要還是鞏固一下自己對SpringMVC對請求及響應的處理做一個細節的總結吧,不知道大家有沒有清楚這個過程。

想熟悉這部分內容最主要的還是要熟悉HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler這兩個接口以及屬性編輯器、數據綁定機制。

本文難免有錯誤,希望讀者能指出來。

參考資料

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]

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/283742.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/283742.shtml
英文地址,請注明出處:http://en.pswp.cn/news/283742.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

instancing render

當要繪制同一個東西很多次的時候&#xff0c;最簡單的想法可能是循環調用glDrawArrays()&#xff0c;但這樣會造成性能的損失。因為當顯卡在渲染一個物體的時候&#xff0c;可能并不需要太多時間&#xff0c;但系統會花大量的時間&#xff0c;頻繁的調用draw命令&#xff0c;再…

對不起,我不是一個自律的人

大家好&#xff0c;我是 &#x1f41f;&#x1f4a8;。前天&#xff0c;星球 的一位大學生朋友問了我幾個問題&#xff1a;你大學時如何安排每日的時間&#xff1f;為什么能學那么多技術&#xff1f;你會學習到很晚嗎&#xff1f;你是如何保持自律的&#xff1f;我覺得這幾個問…

保證接口數據安全的10種方案

前言 大家好&#xff0c;我是程序汪&#xff0c;互聯網項目需要特別注意數據安全&#xff0c;如果你簡歷上是互聯網類型項目&#xff0c;安全方面肯定要能說出個一二三&#xff0c;下面分享下這方面的干貨&#xff0c;大家可以記住幾條&#xff0c;面試時好說道說道 我們日常…

Html5本地存儲LocalStorage

HTML5 提供了兩種在客戶端存儲數據的新方法&#xff1a; localStorage - 沒有時間限制的數據存儲sessionStorage - 針對一個 session 的數據存儲在瀏覽器中打開審查元素&#xff08;如谷歌F12&#xff09;&#xff0c;在Resources下面可以查看里面的數據。 localStorage提供了幾…

python 中的os模塊

python os模塊 Python os 模塊提供了一個統一的操作系統接口函數一、對于系統的操作1、os.name 當前使用平臺其中 ‘nt’ 是 windows&#xff0c;’posix’ 是linux 或者 unix2、os.sep輸出操作系統的特定的路徑分隔符。Win下為“\”&#xff0c;Linux下為“/”3、os.pathsep 輸…

java第一季2.2

2019獨角獸企業重金招聘Python工程師標準>>> 標識符&#xff1a; 是給變量類方法命名的符號、標識符開頭可以_、字母、$命名&#xff0c;不可以用數字命名。關鍵字不可命名&#xff0c;大小寫區分。不可以用非法字符 變量&#xff1a;變量類型。變量名。變量值。如&…

讀《華為數字化轉型之道》

數字化轉型應該很多人都聽過&#xff0c;但如果你做過 ToB 軟件&#xff0c;聽得更多的是信息化&#xff0c;那信息化和數字化是什么關系呢&#xff1f;下面用一個小例子來說說我的理解。記得剛上初中的時候&#xff0c;平時測驗、考試的試卷&#xff0c;都是人工在板上進行刻寫…

Thrift基本原理及使用

參考文章RPC 基本原理與 Apach Thrift 初體驗 RPC基本原理 RPC(Remote Procedure Call)&#xff0c;遠程過程調用&#xff0c;大部分的RPC框架都遵循如下三個開發步驟&#xff1a; 1. 定義一個接口說明文件&#xff1a;描述了對象(結構體)、對象成員、接口方法等一系列信息&am…

01-H5語義化標簽

轉載于:https://www.cnblogs.com/Zeki/p/5901399.html

JSON 解析的兩種方法

今天幫朋友看了下JSON解析結果 eval解析JSON中的注意點在JS中將JSON的字符串解析成JSON數據格式&#xff0c;一般有兩種方式&#xff1a; 1.一種為使用eval()函數。 2. 使用Function對象來進行返回解析。 使用eval函數來解析&#xff0c;并且使用jquery的each方法來遍歷 用jque…

配置中心 App Configuration (三):配置的動態更新

Get Azure key-value pairs from App configuration | Serverless360寫在前面我在前文&#xff1a;《微軟Azure配置中心 App Configuration (一)&#xff1a;輕松集成到Asp.Net Core》已經介紹了Asp.net Core怎么輕易的接入azure 配置中心App Configuration(下稱azure 配置中心…

萬字總結 JS 數據結構與常用的算法

前言 首先&#xff0c;為什么我會學習數據結構與算法呢&#xff0c;其實主要是有兩方面 第一&#xff0c;是我在今年的flag里明確說到我會學這個東西第二&#xff0c;學了這些&#xff0c;對自己以后在工作或者面試也會帶來許多好處然后&#xff0c;本文是最近學習的一個總結文…

精通Java設計模式從初見到相愛之工廠+策略模式(3)

為什么80%的碼農都做不了架構師&#xff1f;>>> 1、公司項目需求。 用戶簽到活動&#xff0c;會員簽到怎么處理&#xff0c;超級會員怎么處理&#xff0c;普通用戶簽到怎么處理&#xff0c;針對不同的檔次&#xff0c;有不同的方案&#xff0c;所以在項目中用到了策…

jquery weui 中alert彈出框在ios中跳動問題

問題描述&#xff1a; jquery-weui中的彈出框在ios上會有一個右下角向中間滑動的效果&#xff0c;在Android上沒有這個效果。 解決方法&#xff1a; 修該jquery-weui.js中的openModal方法如下圖: 轉載于:https://www.cnblogs.com/xianZJ/p/6773097.html

WPF效果第一百九十五篇之又玩ListBox

ListBox一直是我的最愛;今天再次基于他玩耍一下不一樣的效果;閑話不多扯直接看效果:1、這次直接用的ItemContainerStyle:2、通過HitTest實現點選邊框&#xff1a;Point point e.GetPosition(LightDarkListBox); VisualTreeHelper.HitTest(LightDarkListBox, new HitTestFilter…

Web3,互聯網新造神“機器”?

本文來自微信公眾號&#xff1a;每經頭條 &#xff08;ID&#xff1a;nbdtoutiao&#xff09;&#xff0c;作者&#xff1a;李蕾&#xff0c;編輯&#xff1a;肖芮冬&#xff0c;頭圖來自&#xff1a;視覺中國 “與目前的互聯網相比&#xff0c;Web3基于區塊鏈等底層技術&#…

Gradle實戰:發布aar包到maven倉庫

查看原文&#xff1a;http://blog.csdn.net/u0108184... Gradle實戰系列文章&#xff1a;《Gradle基本知識點與常用配置》《Gradle實戰&#xff1a;Android多渠道打包方案匯總》《Gradle實戰&#xff1a;不同編譯類型的包同設備共存》《Gradle實戰&#xff1a;執行sql操作hive…

synchronized與Lock的區別

類別synchronizedLock存在層次Java的關鍵字&#xff0c;在jvm層面上是一個類鎖的釋放1、以獲取鎖的線程執行完同步代碼&#xff0c;釋放鎖 2、線程執行發生異常&#xff0c;jvm會讓線程釋放鎖在finally中必須釋放鎖&#xff0c;不然容易造成線程死鎖鎖的獲取假設A線程獲得鎖&am…

even兼容

var eventarguments.callee.caller.arguments[0]||window.event;//消除瀏覽器差異 var ewindow.event||event; //消除瀏覽器差異 轉載于:https://www.cnblogs.com/webqiand/articles/11250768.html

普通中年人的真實出路

閱讀本文大概需要6分鐘。互聯網人甚至中國整體的用工市場的確有中年淘汰的問題&#xff0c;我們可以當它不存在&#xff0c;甚至當有人給出解法的時候&#xff0c;我們也可以認為他們在傳播焦慮&#xff0c;但事實就是事實&#xff0c;它的存在不隨個人意愿而轉移。最近抖音上有…