如何假裝成一個服務端開發(十) -- Spring MVC 源碼

前言

    在第七篇咱們已經聊過了一些Spring MVC的運行原理,固然大多數人應該仍是和我同樣迷迷糊糊,只知道一個大概的運行過程,這一篇,我想要從源碼的角度更加進一步去了解Spring MVC的整個運行過程。java

springframework源碼調試

    爲了可以進一步瞭解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中的源碼了。

攔截器HandlerInterceptor

    咱們知道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 的攔截器的完成方法,並且順序是先註冊後執行。

 

HttpMessageConverter

    當一個請求來到時,在處理器執行的過程當中,它首先會從 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到底怎麼從請求中獲取參數的問題困擾好久了,這通源碼跟蹤下來,總算略有收穫……不容易啊。

相關文章
相關標籤/搜索