又到了很無聊的時候了,因而隨便看看源碼僞裝本身很努力的樣子,哈哈哈;html
記得上一篇博客隨便說了一下RequestBody的用法以及注意的問題,這個註解做爲很是經常使用的註解,也是時候瞭解一波其中的原理了。前端
舒適提示:閱讀本篇博客,默認你以前大概看過springmvc源碼,懂得其中的基本流程java
1.HttpMessageConverter接口web
這個接口就是@RequestBody和@ResponseBody這兩個註解的精髓,咱們就先看看這個頂層接口定義了哪些方法:spring
public interface HttpMessageConverter<T> { //判斷當前轉換器是否能夠解析前端傳過來的數據 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType); //判斷當前轉換器是否能夠將後端數據解析爲前端須要的格式 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType); //當前轉換器可以解析全部的數據類型 List<MediaType> getSupportedMediaTypes(); //這個方法就是讀取前端傳過來的數據 T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; //將後臺數據轉換而後返回給前端 void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
咱們從這個頂層接口中這幾個方法就大概能看到一些東西,能夠確定的就是在@RequestBody和@ResponseBody這兩個註解原理的內部轉換器應該都是實現了這個HttpMessageConverter,由於這個接口裏又是read,又是write;而後就是在上面我只是簡單說了前端傳過來的數據,返回給前端須要的格式這種模糊的說法,爲何不直接說返回json格式的數據呢?json
哈哈,可能有的小夥伴會說,瑪德,你絕逼是怕說錯,才說這些模糊的說法;咳,固然有部分是這個意思,可是最大的緣由就是上面方法參數中有個類MediaType,你打開看看就知道了,這裏定義了不少的能夠解析的數據類型,好比"application/json","application/xml","image/gif","text/html"....等等,還有好多聽都沒聽過的;後端
其實瞭解http請求的小夥伴應該已經看出來了,這裏這些數據類型就是下圖所示的這些;固然,咱們暫時只關注json的,至於其餘類型的怎麼解析有興趣的小夥伴能夠研究研究;數組
2.HandlerMethodArgumentResolver接口springboot
咱們看看這個接口,看名字就知道應該是方法參數解析器,很明顯就是用於解析方法Controller中方法的參數的,仍是簡單看看這個接口中的方法:mvc
public interface HandlerMethodArgumentResolver { //該解析器是否支持解析Controller中方法中的參數,由於這裏參數類型能夠是簡單類型,也能夠是集合等類型 boolean supportsParameter(MethodParameter parameter); //開始解析Http請求中的數據,解析出來的數據要和方法參數對應 Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; }
這個接口定義的方法做用其實就是將http請求中的參數對應到Controller中的參數
3.HandlerMethodReturnValueHandler接口
public interface HandlerMethodReturnValueHandler { //這個方法判斷該處理器是否支持返回值類型,這裏的返回值就是controller方法執行後的返回值 boolean supportsReturnType(MethodParameter returnType); //將controller方法的返回值進行解析成前端須要的格式,後續就會丟給前端 void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception; }
這個接口的做用:好比一個Controller方法中的返回值是一個集合,那麼springmvc內部在將數據返回給前端以前,就會先拿到全部的返回值解析器,而後遍歷每個,分別執行supportsReturnType方法,看看哪一個解析器能夠解析集合類型,找到解析器以後,而後再執行handleReturnValue方法解析就好了,其實2中的方法參數解析器也是這樣的一個步驟
4.ServletInvocableHandlerMethod類
這個類是幹什麼的呢?看過springmvc源碼的人應該知道一點,仍是簡單說說吧,在springmvc中會將controller中的每一個被RequestMapping註解修飾的方法(也能夠叫作處理器)給封裝成ServletInvocableHandlerMethod類,封裝後想要執行該處理器方法只須要執行該類的invokeAndHandle方法;
請注意:就是在invokeAndHandle這個方法中會調用:調用方法參數解析器------>執行處理器方法-------->調用返回值解析器、
能夠簡單看看源碼,請必定要了解springmvc的流程,由於我不會從頭至尾講一遍,咱們直接從DispatcherServlet中的doDispatch方法的ha.handle(xxx)這裏提及,這裏主要是執行處理器適配器的handle方法,這裏具體的處理器適配器實現是:AbstractHandlerMethodAdapter
咱們進入AbstractHandlerMethodAdapter這個適配器的handle方法看看(^o^)/:
//能夠看到這裏就是調用了handleInternal方法,而handleInternal方法未實現 public final ModelAndView handle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } //這個方法在子類RequestMappingHandlerAdapter中實現 protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception;
接下來咱們看看RequestMappingHandlerAdapter中實現的handleInternal方法(」゜ロ゜)」:
protected final ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception { //省略跟邏輯無關的代碼 ....... ......... return invokeHandlerMethod(request, response, handlerMethod); }
進入invokeHandlerMethod方法看看(´・_・`):
private ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response,HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); //省略一些代碼 //就是再這裏去執行Controller中的處理器方法 requestMappingMethod.invokeAndHandle(webRequest, mavContainer); //此處省略好多代碼 }
繼續進入到invokeAndHandle方法內部看看╮(╯_╰)╭:
public final void invokeAndHandle(NativeWebRequest request, ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception { //請注意,這裏面就是執行handler方法的位置 Object returnValue = invokeForRequest(request, mavContainer, providedArgs); //省略一些代碼 try { //這裏就是執行咱們前面說的返回值解析器 returnValueHandlers.handleReturnValue(returnValue, getReturnType(), mavContainer, request); } //省略一些代碼 }
看看invokeForRequest方法你就能看到有趣的東西ヽ(」`▽´)ノ
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { //這裏就是從request中拿到參數,利用方法參數解析器進行解析,映射到方法參數中,這個方法就在下面 Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); //省略一些代碼 //這裏就是根據上一步將請求參數映射處處理器方法參數中,而後執行對應的處理器方法 Object returnValue = doInvoke(args); //省略一些代碼 return returnValue; } private Object[] getMethodArgumentValues(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception { //這裏獲取匹配到的handler方法的參數數組,每一個參數在以前都被封裝成了一個MethodParameter對象,而後再遍歷這個數組,將其中每一個MethodParameter和http請求提供的參數進行比較,
至於怎麼比較,就會用到以前說的參數解析器的那個support方法 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (this.argumentResolvers.supportsParameter(parameter)) { try { args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } //省略一些代碼 return args; }
其實到這裏,有木有感受清晰一點了,那麼確定有小夥伴要問了,說了半天,你仍是沒有說json是怎麼解析的啊?
不要急,咱們先把大概的流程過一遍以後,後面的都是小問題,那麼,咱們的轉換器是在哪裏轉換的呢?
欲知後事如何,請日後面看
5.無題
在4中咱們重點看兩個地方,第一個地方:this.argumentResolvers.supportsParameter(parameter);第二個地方:this.argumentResolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
5.1.RequestResponseBodyMethodProcessor
第一個地方,咱們點進去supportsParameter方法,
而後咱們進入getArgumentResolver方法內部(´□`川):
@Nullable private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); if (result == null) { //for循環遍歷全部的方法參數解析器, for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
//省略一些代碼
//判斷哪個解析器支持解析request中的參數,這裏很關鍵,由於衆多解析器中其中有一個參數解析器是RequestResponseBodyMethodProcessor,
//下面咱們看看這個解析器的supportParameter方法 if (methodArgumentResolver.supportsParameter(parameter)) { result = methodArgumentResolver; this.argumentResolverCache.put(parameter, result); break; } } } return result; }
RequestResponseBodyMethodProcessor的supportsParameter方法,這裏想必能看得懂吧,就是看Controller中的處理器方法中的參數前面有沒有RequestBody註解,有註解,那麼這個RequestResponseBodyMethodProcessor解析器就會生效
對了,補充一點,這個解析器RequestResponseBodyMethodProcessor但是同時實現了方法參數解析器接口、返回參數解析器接口的哦,這說明了處理返回值解析器用的也是這個解析器
5.2.執行argumentResolvers.resolveArgument()方法
//這個方法就到了最關鍵的地方了,注意了注意了 public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,NativeWebRequest webRequest, WebDataBinderFactory binderFactory)
throws Exception { //獲取一個轉換器,用於讀取前端傳過來的json數據 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getParameterType());
//獲取handler方法形參中的全部註解,例如@Valid。@PathVariable等 Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) { //判斷若是是以valid開頭的註解,其實就是@Valid註解或者是@Validated註解,那麼就會去校驗是否符合規則嘛,這個不用多說 if(annot.annotationType().getSimpleName().startsWith("Valid")) { String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); Object hints = AnnotationUtils.getValue(annot); binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); BindingResult bindingResult = binder.getBindingResult(); if (bindingResult.hasErrors()) { throw new MethodArgumentNotValidException(parameter, bindingResult); } } } return arg; }
最後咱們只須要輕輕點開readWithMessageConverters方法,就能看到更有意思的東西~^o^~
6.xxxConverter
咱們進入readWithMessageConverters這個方法,
@SuppressWarnings("unchecked") protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Class<T> paramType) throws IOException, HttpMediaTypeNotSupportedException { //這裏回答了本篇最開始說的MediaType 究竟是什麼東西,從哪裏獲取的,很明顯是從請求頭中的ContentType獲取的 MediaType contentType = inputMessage.getHeaders().getContentType(); if (contentType == null) { contentType = MediaType.APPLICATION_OCTET_STREAM; } //獲取全部的HttpMessageConverter,遍歷,看看哪個轉換器支持前端傳過來的數據類型 for (HttpMessageConverter<?> messageConverter : this.messageConverters) { if (messageConverter.canRead(paramType, contentType)) {
//調用對應的轉換器的read方法去把前端傳過來的json字符串轉爲java對象 return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage); } } //省略一些代碼 }
到了這裏確定有人會說,那到底用的是哪個轉換器呢?難道是要咱們本身導入?仍是默認已經導入轉換器了呢?
固然是默認就爲你初始化了一些轉換器了啊,若是你想自定義也行,並且仔細看看下圖中跟json有關的只有MappingJackson2HttpMessageConverter這個轉換器了,可想而知這個轉換器(其實是這個轉換器的父類方法中才有具體的操做)中使用的就是開源的Jackson來將json字符串轉爲java對象的,有興趣瞭解的可使用一下Jackson本身嘗試一下;
偷偷告訴你(҂ ˘ _ ˘ ),springboot默認已經導入了Jackson包,若是你後期想用其餘的轉換器,只須要導入相關依賴就ok了;
7.結束
這篇博客到這裏就差很少了,寫了很久,邊寫邊查資料,能夠說每次看源碼都能學到新的東西,固然,我也沒有死磕精神,不是不想,主要是沒有那個水平,哈哈;
其實還要寫還能寫,不是還有個@ResponseBody原理還沒說嗎?其實跟@RequestBody大同小異的,有興趣的能夠在第4點最後的代碼中返回值參數處理器方法,這裏就是入口
話說還有個問題沒有解決,原本我想找一下最後的那幾個轉換器初始化時機的,而後實在是找不出來啊,查了一下資料,都說是在處理器適配器的構造器中初始化這些轉換器的,哎,jdk1.8,我在適配器中找了很久,愣是沒找到,打斷點也調試不出來,把本身坑了很久;
有沒有大哥知道初始化時機的,評論一下,謝謝了(ㄒoㄒ)