當一個http請求來臨時,SpringMVC究竟偷偷幫你做了什麼?SpringMVC視圖處理器與視圖篇章【終章】

本篇文章依舊是基於上篇文章的基礎做的描述,請先觀看

  1. 當一個http請求來臨時,SpringMVC究竟偷偷幫你做了什麼?請求映射器篇

  2. 當一個http請求來臨時,SpringMVC究竟偷偷幫你做了什麼?SpringMVC處理器適配器與處理器篇章

先上圖:

上一篇文章,我將 Handler處理器適配器處理器做了一個很詳細流程分析,那麼本篇文章會圍繞視圖解析器視圖兩個流程來分析源碼!

1. 視圖推斷源碼分析

不知道大家對這一段代碼是否熟悉!

public void invokeAndHandle(ServletWebRequest webRequest, 
                    ModelAndViewContainer mavContainer,
					Object... providedArgs) throws Exception {
	
    //反射執行映射方法獲取方法返回值
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    setResponseStatus(webRequest);

    //......忽略無關代碼
    try {
        //視圖推斷(到底是直接響應還是返回一個視圖)
        this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), 
            mavContainer, webRequest);
    }
    catch (Exception ex) {
        //......忽略無關代碼
    }
}

沒錯,這個正是上篇文章,處理器,裏面分析的,反射執行映射方法的主邏輯,當方法返回結果之後,會根據返回值進行視圖推斷,推斷該方法到底該以一個什麼樣的方式去返回給調用方!

我們進入到這個方法內部看一下

this.returnValueHandlers.handleReturnValue(returnValue, 
                      getReturnValueType(returnValue), mavContainer, webRequest);
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,                              ModelAndViewContainer mavContainer,                                                             NativeWebRequest webRequest) throws Exception {​    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);    if (handler == null) {        throw new IllegalArgumentException("Unknown return value type: "                                                                                       + returnType.getParameterType().getName());    }    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);}java

HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);

這段代碼邏輯主要是爲了篩選出當前方法執行返回值所需的處理器,基於什麼篩選呢?

先大概看一下有多少對應的處理器!

我們平常使用的註解@RestController,@ResponseBody等都是使用的 RequestResponseBodyMethodProcessor處理器

@Override
public boolean supportsReturnType(MethodParameter returnType) {
    return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass()
                                                , ResponseBody.class) ||
            returnType.hasMethodAnnotation(ResponseBody.class));
}

其實判斷的邏輯很簡單,就是判斷方法上又沒有增加ResponseBody註解罷了!

選擇對應的處理器之後,開始使用對應的處理器處理方法的返回值!

handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest)
						    throws IOException, HttpMediaTypeNotAcceptableException,
														HttpMessageNotWritableException {

    mavContainer.setRequestHandled(true);
    //獲取輸入流
    ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
    //獲取輸出流
    ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
    //開始判斷視圖並寫出到頁面
    writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}

writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);

這裏判斷該方法的返回值,是String還是以個其他對象,其他對象要基於策略進行處理,如JSON化處理,而String則會直接返回給頁面!因爲這裏使用的是RequestResponseBodyMethodProcessor所以他會直接將該值寫到界面。

如果你再Controllrt中的某個方法返回了一個地址,比如retunr index;你的本意是要跳轉到根目錄下的 index頁面,那麼此時將會使用ViewNameMethodReturnValueHandler處理器,返回一個ModelAndView則使用ModelAndViewMethodReturnValueHandler處理器,諸如此類的處理器足足有15個另外還可以自己擴展,所以其他的我就不多說了,需要讀者自己調試源碼觀看!而關於添加@ResponseBody的返回方式,其實上篇文章已經說過了,本篇文章以返回一個頁面路徑爲例(即使用ViewNameMethodReturnValueHandler處理)

大概看一眼代碼邏輯!

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, 
                              NativeWebRequest webRequest) throws Exception {

    if (returnValue instanceof CharSequence) {
        //首先獲取方法的返回值,返回值即是將要跳轉的路徑
        String viewName = returnValue.toString();
        mavContainer.setViewName(viewName);
        //判斷是否是重定向,如果是重定向會將下面這個屬性設置爲true 而判斷是否重定向的邏輯也很簡單
        if (isRedirectViewName(viewName)) {
            mavContainer.setRedirectModelScenario(true);
        }
    }
    else if (returnValue != null) {
        //返回值不是字符串,直接拋出異常
    }
}

isRedirectViewName(viewName)

protected boolean isRedirectViewName(String viewName) {
    return (PatternMatchUtils.simpleMatch(this.redirectPatterns, viewName) 
            || viewName.startsWith("redirect:"));
}

其實就是判斷 返回值的前綴是不是以redirect:開頭的!相信你們再使用mvc進行重定向操作的時候是不是這樣寫?return "redirect:index";,這裏就是爲什麼要這樣寫的原因!

處理完成之後,返回到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod方法的org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView方法內部!

@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
                                     ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    //從容器裏面獲取model
    ModelMap model = mavContainer.getModel();
    //根據返回路徑設置爲modelandview的viewName,設置進model,和狀態碼
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), 
                                        model, mavContainer.getStatus());
    if (!mavContainer.isViewReference()) {
        mav.setView((View) mavContainer.getView());
    }
    if (model instanceof RedirectAttributes) {
        Map<String, ?> flashAttributes = 
            ((RedirectAttributes) model).getFlashAttributes();
        HttpServletRequest request = webRequest.
            								getNativeRequest(HttpServletRequest.class);
        if (request != null) {
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
    }
    return mav;
}

封裝成一個ModelAndView後,一路返回到org.springframework.web.servlet.DispatcherServlet#processDispatchResult

首先判斷是否有異常,如果有異常會調用默認的錯誤視圖解析器!

if (exception != null) {
    if (exception instanceof ModelAndViewDefiningException) {
        logger.debug("ModelAndViewDefiningException encountered", exception);
        mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    }
    else {
        Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
    }
}

沒有異常則進入resolveViewName方法 尋找視圖解析器並返回視圖解析器!

@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
                               				Locale locale, HttpServletRequest request) 
    																throws Exception {

    if (this.viewResolvers != null) {
        //循環遍歷視圖解析器
        for (ViewResolver viewResolver : this.viewResolvers) {
            //尋找對應的視圖解析器
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                //返回對應的視圖解析器
                return view;
            }
        }
    }
    return null;
}

一路返回到org.springframework.web.servlet.DispatcherServlet#doDispatch而後進入org.springframework.web.servlet.view.AbstractView#render方法

@Override
public void render(@Nullable Map<String, ?> model, HttpServletRequest request,
                   HttpServletResponse response) throws Exception {

    if (logger.isDebugEnabled()) {
        logger.debug("View " + formatViewName() +
                     ", model " + (model != null ? model : Collections.emptyMap()) +
                     (this.staticAttributes.isEmpty() ? "" : ", static attributes " + 								this.staticAttributes));
    }

    Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
    //檢測model有沒有寫出去
    prepareResponse(request, response);
    //準備開始響應頁面
    renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}

renderMergedOutputModel

// 獲取目標資源 
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
    throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
                               "]: Check that the corresponding file exists within your web application archive!");
}

// 不知道大家還記得進行servlet開發的時候,我們再將頁面write回顯之後需要cloes關閉Out資源,這裏其實就是再作者判斷,如果我麼們手動關閉了,那麼將會執行`rd.include(request, response);`負責執行rd.forward(request, response);方法 這個方法會包含關閉方法!
if (useInclude(request, response)) {
    response.setContentType(getContentType());
    if (logger.isDebugEnabled()) {
        logger.debug("Including [" + getUrl() + "]");
    }
    rd.include(request, response);
}
//這個方法會包含關閉方法!
else {
    if (logger.isDebugEnabled()) {
        logger.debug("Forwarding to [" + getUrl() + "]");
    }
    rd.forward(request, response);
}

將視圖寫出去之後,再將流關閉之後,講道理整個流程就結束了!我們畫個圖總結一下全篇!

獲取原圖請關注公衆號【JAVA程序狗】回覆SpringMvc 獲取!


才疏學淺,如果文章中理解有誤,歡迎大佬們私聊指正!歡迎關注作者的公衆號,一起進步,一起學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章