在第七篇咱們已經聊過了一些Spring MVC的運行原理,固然大多數人應該仍是和我同樣迷迷糊糊,只知道一個大概的運行過程,這一篇,我想要從源碼的角度更加進一步去了解Spring MVC的整個運行過程。java
爲了可以進一步瞭解spring的運行過程,debug源碼固然是不二的選擇。網上搜索的話仍是能找到一些資料的,可是感受方法都比較複雜。這裏發現一種比較簡單的源碼調試方案,拿出來分享一下。git
首先須要準備一個能夠運行的Spring Boot項目,好比咱們redis一篇中準備的db項目就是個不錯的選擇。固然還須要咱們的調試環境,這裏用的是idea做爲調試環境。github
而後還須要下載springframework的源碼。web
而後查看咱們springframework依賴的版本。若是直接在pom.xml中查找,發現咱們並無明確指定版本。redis
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.1.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
這個東西幫咱們自動選擇了版本。spring
咱們能夠在左上角選擇項目展現模式,選擇projectjson
而後再External Libraries中查看依賴版本mvc
好比這裏咱們依賴了5.1.3.RELEASE版本。app
而後將咱們從github下載的源碼切換到對應的tag (git checkout v5.1.3.RELEASE)框架
而後再IDEA中打開項目的File -> Project Structure頁面,選擇Libraries頁面,選擇咱們要調試的庫,好比要DispatcherServlat所在的包在右側顯示大概是這樣的
咱們看到Soures是紅色的,表示找不到相關的Source,選中sources,而後點擊下方的 + 按鈕,最後選擇下載下來的spring-framework的源碼中響應的module,好比spring-webmvc對應的目錄就是
而後在IDEA中打開DispatcherServlat類。CMD + O 輸入類名能夠跳轉到類。若是你不指定源碼,這個時候就會進入.class文件,可是若是已經制定源碼,那麼就會打開.java文件,這個時候就和普通的debug同樣了,咱們能夠debug springframework中的源碼了。
咱們知道Spring MVC的核心類就是 DispatcherServlet 做爲一個Servlet,核心方法就是onService用來接收請求提供服務。而onService 又會調用doDispatch方法。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { try { ModelAndView mv = null; Object dispatchException = null; try { //multipart request處理,會使用默認提供的 StandardServletMultipartResolver 類 processedRequest = this.checkMultipart(request); multipartRequestParsed = processedRequest != request; //解析request,獲取 HandlerExecutionChain (他會包含一個處理器和HandlerInterceptor) mappedHandler = this.getHandler(processedRequest); if (mappedHandler == null) { this.noHandlerFound(processedRequest, response); return; } //根據 HandlerExecutionChain 選擇一個HandlerAdapter 準備處理請求 HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler()); String method = request.getMethod(); //這段代碼不知道在幹嗎..... boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) { return; } } //調用 HandlerExecutionChain 中HandlerInterceptor 的 perHandler方法查看是否須要攔截 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } //經過handlerAdapter調用HandlerExecutionChain中的處理器,返回ModelAndView視圖處理器 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } this.applyDefaultViewName(processedRequest, mv); //調用 HandlerExecutionChain 中HandlerInterceptor 的 postHandler mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception var20) { dispatchException = var20; } catch (Throwable var21) { dispatchException = new NestedServletException("Handler dispatch failed", var21); } //對返回結果作最後處理 (內部會調用 HandlerInterceptor 的 afterCompletion) this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException); } catch (Exception var22) { //報錯時調用 HandlerInterceptor 的 afterCompletion this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22); } catch (Throwable var23) { this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23)); } } finally { if (asyncManager.isConcurrentHandlingStarted()) { if (mappedHandler != null) { mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response); } } else if (multipartRequestParsed) { this.cleanupMultipart(processedRequest); } } }
萬幸,DispatcherServlet中核心方法的代碼行數並不算爆炸。並且有了上一篇的瞭解,這裏的調用過程也基本可以一一對應。
上面有總體步驟屢次調用了HandlerInterceptor。因此咱們先來分析下這個攔截器。
public interface HandlerInterceptor { // 處理器執行前方法 default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } // 處理器處理後方法 default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } // 處理器完成後方法 (包括返回渲染) default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { } }
攔截器的方法並很少,而且也不難理解。總體流程大概是這樣
咱們定義一個簡單的攔截器
//有default實現,不必定須要重寫方法 public class Interceptor1 implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { System.out.println("處理器前方法"); // 返回true,不會攔截後續的處理 return true; } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("處理器後方法"); } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("處理器完成方法"); } }
而後,咱們須要將它註冊到Spring MVC框架中。
@Configuration public class WebAppConfigurer implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { // 可添加多個 registry.addInterceptor(new Interceptor1()) //interceptor的做用返回,只會做用在 /interceptor/* 地址下面 .addPathPatterns("/interceptor/*"); } .... }
假設咱們定義三個攔截器,而且按照 1 2 3的順序進行註冊。那麼運行順序大概以下
【MulitiInterceptor1】處理器前方法 【MulitiInterceptor2】處理器前方法 【MulitiInterceptor3】處理器前方法 執行處理器邏輯 【MulitiInterceptor3】處理器後方法 【MulitiInterceptor2】處理器後方法 【MulitiInterceptor1】處理器後方法 視圖渲染 【MulitiInterceptor3】處理器完成方法 【MulitiInterceptor2】處理器完成方法 【MulitiInterceptor1】處理器完成方法
對於處理器前方法採用先註冊先執行,而處理器後方法和完成方法則是先註冊後執行的規則。
當咱們的攔截器2 的preHandler返回 false的時候,運行就不太同樣了。
【MulitiInterceptor1】處理器前方法 【MulitiInterceptor2】處理器前方法 【MulitiInterceptor1】處理器完成方法
處理器前(preHandle)方法會執行,可是一旦返回 false,則後續的攔截器、處理器和全部攔截器的處理器後(postHandle)方法都不會被執行。完成方法 afterCompletion 則不同,它只會執行返回 true 的攔截器的完成方法,並且順序是先註冊後執行。
當一個請求來到時,在處理器執行的過程當中,它首先會從 HTTP 請求和上下文環境來獲得參數。若是是簡易的參數它會以簡單的轉換器進行轉換,而這些簡單的轉換器(Converter)是 Spring MVC 自身已經提供了的。可是若是是轉換 HTTP 請求體(Body),它就會調用 HttpMessageConverter接口的方法對請求體的信息進行轉換。
HttpMessageConverter 其實就是將 HttpServletRequest 中的數據, 根據 MediaType 轉換成指定格式的數據, 好比咱們常見的表單提交 或經過 Json字符串提交數據。
1. FormHttpMessageConverter 支持 MultiValueMap 類型, 而且 MediaType 類型是 "multipart/form-data", 從 InputStream 裏面讀取數據, 並經過&符號分割, 最後轉換成 MultiValueMap, 或 將 MultiValueMap轉換成 & 符號鏈接的字符串, 最後轉換成字節流, 輸出到遠端 2. BufferedImageHttpMessageConverter 支持 BufferedImgae 的 HttpMessageConverter, 經過 ImageReader 將 HttpBody 裏面的數據轉換成 BufferedImage, 或ImageWriter 將ImageReader 轉換成字節流輸出到 OutputMessage 3. StringHttpMessageConverter 支持數據是 String 類型的, 從 InputMessage 中讀取指定格式的 str, 或 將數據編碼成指定的格式輸出到 OutputMessage 4. SourceHttpMessageConverter 支持 DOMSource, SAXSource, StAXSource, StreamSource, Source 類型的消息轉換器, 在讀取的時候, 從 HttpBody 裏面讀取對應的數據流轉換成對應對應, 輸出時經過 TransformerFactory 轉換成指定格式輸出 5. ResourceHttpMessageConverter 支持數據類型是 Resource 的數據, 從 HttpBody 中讀取數據流轉換成 InputStreamResource|ByteArrayResource, 或從 Resource 中讀取數據流, 輸出到遠端 6. ProtobufHttpMessageConverter 支持數據類型是 com.google.protobuf.Message, 經過 com.google.protobuf.Message.Builder 將 HttpBody 中的數據流轉換成指定格式的 Message, 經過 ProtobufFormatter 將 com.google.protobuf.Message 轉換成字節流輸出到遠端 7. ObjectToStringHttpMessageConverter 支持 MediaType是 text/plain 類型, 從 InputMessage 讀取數據轉換成字符串, 經過 ConversionService 將字符串轉換成自定類型的 Object; 或將 Obj 轉換成 String, 最後 將 String 轉換成數據流 8. ByteArrayHttpMessageConverter 支持格式是 byte 類型, 從 InputMessage 中讀取指定長度的字節流, 或將 OutputMessage 轉換成字節流 9. AbstractXmlHttpMessageConverter及其子類 支持從 xml 與 Object 之間進行數據轉換的 HttpMessageConverter 10. AbstractGenericHttpMessageConverter 支持從 Json 與 Object 之間進行數據轉換的 HttpMessageConverter (PS: 主要經過 JackSon 或 Gson) 11. GsonHttpMessageConverter 支持 application/*++json 格式的數據, 並經過 Gson, 將字符串轉換成對應的數據 12. MappingJackson2XmlHttpMessageConverter 持 application/*++json/*+xml 格式的數據, 並經過 JackSon, 將字符串轉換成對應的數據
接口源碼以下
public interface HttpMessageConverter<T> { // 是否可讀,其中clazz爲Java類型,mediaType爲HTTP請求類型 boolean canRead(Class<?> clazz, MediaType mediaType); // 判斷clazz類型是否可以轉換爲mediaType媒體類型 // 其中clazz爲java類型,mediaType爲HTTP響應類型 boolean canWrite(Class<?> clazz, MediaType mediaType); // 可支持的媒體類型列表 List<MediaType> getSupportedMediaTypes(); // 當canRead驗證經過後,讀入HTTP請求信息 T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException; //當canWrite方法驗證經過後,寫入響應 void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException; }
關於HttpMessageConverter的調用邏輯實際上仍是比較深的而且是有條件的,當參數又@RequestBody 纔會調用read相關方法,當方法註解了@ResponseBody以後,纔會調用相關的write方法。
假設咱們如今運行了Controller中的一個方法
@PostMapping("/insertUser") @ResponseBody public User insertUser(@RequestBody User user){..}
不關心方法內部,而是看看spring是如何讓運做起來的,咱們從doDispatch mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 開始。
使用的ha是RequestMappingHandlerAdapter,進一步跟蹤,會發現調用了其中的invokeHandlerMethod方法
/** * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView} * if view resolution is required. * @since 4.2 * @see #createInvocableHandlerMethod(HandlerMethod) */ @Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { //建立一個參數解析工廠,用於解析參數 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); //保存最終須要調用的controller方法,包括bean,參數類型等。 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); //相似於構造一個調用controller的環境,配置一些必要組件 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } ....... // 經過構造的環境調用最終方法 invocableMethod.invokeAndHandle(webRequest, mavContainer); .... } finally { webRequest.requestCompleted(); } }
繼續跟蹤,發現調用了InvocableHandlerMethod.getMethodArgumentValues來建立參數列表。
/** * Get the method argument values for the current request, checking the provided * argument values and falling back to the configured argument resolvers. * <p>The resulting array will be passed into {@link #doInvoke}. * @since 5.1.2 */ protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { if (ObjectUtils.isEmpty(getMethodParameters())) { return EMPTY_ARGS; } //MethodParameter 類用來描述一個參數 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] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } //核心方法,判斷是否可以找到相應的HandlerMethodArgumentResolver來進行參數處理 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { //處理參數 args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled.. if (logger.isDebugEnabled()) { String error = ex.getMessage(); if (error != null && !error.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, error)); } } throw ex; } } return args; }
關於 supportsParameter 實際上就是遍歷全部注入的 HandlerMethodArgumentResolver 對象,調用他們的supportsParameter方法輸入來判斷是否可以處理這個參數。當遍歷到RequestResponseBodyMethodProcessor的時候發現可以處理。順帶一提,它的supportsParameter方法很簡單
@Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(RequestBody.class); }
就是判斷這個參數是否是帶了RequestBody。
而後下面代碼會進一步調用 RequestResponseBodyMethodProcessor.readWithMessageConverters。
關鍵來了
//魔法時刻,獲取Request的 ServletInputStream ,而後傳入HttpMessageConverter進行解析 @Override protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter, Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException { HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); Assert.state(servletRequest != null, "No HttpServletRequest"); ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest); //調用全部注入的 HttpMessageConverter 的canRead和 read方法 Object arg = readWithMessageConverters(inputMessage, parameter, paramType); if (arg == null && checkRequired(parameter)) { throw new HttpMessageNotReadableException("Required request body is missing: " + parameter.getExecutable().toGenericString(), inputMessage); } return arg; }
因爲這裏我post傳入的json,因此選擇了MappingJackson2HttpMessageConverter進行參數解析。
對於非POST的參數,好比get裏面的userId參數,那麼HandlerMethodArgumentResolver 就不會匹配到RequestResponseBodyMethodProcessor,而是會匹配到RequestParamMethodArgumentResolver。
關於canWrite和write差很少也是這個流程,這裏就再也不詳解了。
PS: 被Spring到底怎麼從請求中獲取參數的問題困擾好久了,這通源碼跟蹤下來,總算略有收穫……不容易啊。