controller调用controller的方法_你想过 Controller 这些方法里的参数是如何工作的吗?...
點擊上方“方志朋”,選擇“設為星標”
回復”666“獲取新整理的面試文章
前言
SpringMVC是目前主流的Web MVC框架之一。SpringMVC中Controller的方法參數可以是Integer,Double,自定義對象,ServletRequest,ServletResponse,ModelAndView等等,非常靈活。本文將分析SpringMVC是如何對這些參數進行處理的,使讀者能夠處理自定義的一些參數。
現象
本文使用的demo基于maven。我們先來看一看對應的現象。?
@Controller@RequestMapping(value = "/test")public class TestController {@RequestMapping("/testRb")@ResponseBody
??public 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中兩個重要的接口。
兩個接口分別對應請求方法參數的處理、響應返回值的處理,分別是HandlerMethodArgumentResolver和HandlerMethodReturnValueHandler,這兩個接口都是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:
?使用@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中加入自定義屬性編輯器。
@InitBinderpublic?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是ExtendedServletRequestDataBinder
????????WebDataBinder 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 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
熱門內容:
項目實踐:SpringBoot三招組合拳,手把手教你打出優雅的后端接口
一次SQL查詢優化原理分析
start.aliyun.com正式上線
驚呆了,Spring Boot居然這么耗內存!
兩小時入門Docker
最近面試BAT,整理一份面試資料《Java面試BAT通關手冊》,覆蓋了Java核心技術、JVM、Java并發、SSM、微服務、數據庫、數據結構等等。
獲取方式:點“在看”,關注公眾號并回復?666?領取,更多內容陸續奉上。
明天見(。・ω・
超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生總結
以上是生活随笔為你收集整理的controller调用controller的方法_你想过 Controller 这些方法里的参数是如何工作的吗?...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: anaconda 更改路径_Anacon
- 下一篇: 嵌入式论文3000字_SCI英文论文一般