又到了很無聊的時候了,於是隨便看看源碼假裝自己很努力的樣子,哈哈哈;
記得上一篇博客隨便說了一下RequestBody的用法以及注意的問題,這個注解作為非常常用的注解,也是時候了解一波其中的原理了。
溫馨提示:閱讀本篇博客,默認你之前大概看過springmvc源碼,懂得其中的基本流程
1.HttpMessageConverter接口
這個接口就是@RequestBody和@ResponseBody這兩個注解的精髓,我們就先看看這個頂層接口定義了哪些方法:
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格式的數據呢?
哈哈,可能有的小伙伴會說,瑪德,你絕逼是怕說錯,才說這些模糊的說法;咳,當然有部分是這個意思,但是最大的原因就是上面方法參數中有個類MediaType,你打開看看就知道了,這里定義了很多的可以解析的數據類型,比如"application/json","application/xml","image/gif","text/html"....等等,還有好多聽都沒聽過的;
其實了解http請求的小伙伴應該已經看出來了,這里這些數據類型就是下圖所示的這些;當然,我們暫時只關注json的,至於其他類型的怎么解析有興趣的小伙伴可以研究研究;
2.HandlerMethodArgumentResolver接口
我們看看這個接口,看名字就知道應該是方法參數解析器,很明顯就是用於解析方法Controller中方法的參數的,還是簡單看看這個接口中的方法:
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ㄒ)