SpringMVC源碼解析(五)——視圖處理

前言

    本篇將分析一次請求從接收處處理的最終環節——視圖處理,也是 SpringMVC 源碼解析的最後一節。將涉及異常處理視圖轉發兩部分。java

 

源碼解讀

    承接上篇,來看 「processDispatchResult」 的實現。web

public class DispatcherServlet extends FrameworkServlet {

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                       HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {
        boolean errorView = false;

        // 異常處理
        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);
            }
        }

        if (mv != null && !mv.wasCleared()) {
            // 關注此方法:視圖轉發
            render(mv, request, response);
            if (errorView) {
                WebUtils.clearErrorRequestAttributes(request);
            }
        } else {
            ...// 省略日誌
        }
        // 併發處理
        if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
            return;
        }

        if (mappedHandler != null) {
            // 調用攔截器的 afterCompletion
            mappedHandler.triggerAfterCompletion(request, response, null);
        }
    }
}

    總體來看,該方法有三個主要步驟:異常處理、視圖轉發、攔截器調用。其中攔截器調用邏輯簡單,就是遍歷調用攔截器的 afterCompletion 方法。因此重點來看前二者的實現。數組

 

異常處理

protected ModelAndView processHandlerException(HttpServletRequest request, 
                     HttpServletResponse response, Object handler, Exception ex) throws Exception {
        ModelAndView exMv = null;
        // 遍歷全部註冊的 HandlerExceptionResolver(按優先級),調用其 resolveException
        // 直到有一個解析器處理後返回非空的 ModelAndView
        for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
            exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
        // 若是 exMv != null,說明被解析器捕獲並處理
        if (exMv != null) {
            if (exMv.isEmpty()) {
                request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
                return null;
            }
            // 若是被處理返回的 ModelAndView未指定頁面,則使用默認頁面
            if (!exMv.hasView()) {
                exMv.setViewName(getDefaultViewName(request));
            }
            ...// 省略日誌

            WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
            // 返回異常處理視圖
            return exMv;
        }

        // 沒有被捕獲的就會拋出異常
        throw ex;
    }

    這裏的 HandlerExceptionResolver 就是 Dispatcher 初始化策略中,initHandlerExceptionResolvers 方法中註冊的,默認會掃描註冊全局 HandlerExceptionResolver 類型的實例。先來回顧下 SpringMVC 處理異常的幾種方式:緩存

  • 方式一:自定義 HandlerExceptionResolver ,實現 resolveException 方法處理異常;
  • 方式二:在 Controller 層會拋出預知異常的類下,定義異常處理方法(名稱隨意),使用 @ExceptionHandler 標識,value 屬性指定什麼樣的異常會被該方法處理,使用入參接收捕獲的異常實例;
  • 方式三:定義全局 Controller 加強,在一個被 @ControllerAdvice 標識的類下,同方式二相同的異常處理;
  • 方式四:在自定義異常上使用 @ResponseStatus 標識,value 指定響應碼,reason 指定提示信息。(對應 ResponseStatusExceptionResolver ,不展開講解);
  • 方式五:註冊 SimpleMappingExceptionResolver,」exceptionMappings「 屬性配置異常與響應View的映射關係,」defaultErrorView「 屬性配置默認響應頁面(即」exceptionMappings「未涉及到的異常響應頁面)

    方式一可能比較侷限,一個類只能針對某種類型異常的處理;方式2、方式三比較方便,共同點都是藉助了 @ExceptionHandler 註解,value 能夠指定異常類型數組;方式四通常用於異常是自定義的場景;方式五是配置方式的,能夠經過基於 xml 文件配置,也能夠經過 SpringBoot 的基於代碼配置。併發

    這裏僅分析下來方式二方式三的實現原理,這也是比較經常使用的兩種方式:app

public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
                                         Object handler, Exception ex) {

        // 判斷是否支持該 Handler的異常處理
        if (shouldApplyTo(request, handler)) {
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Resolving exception from handler [" + handler + "]: " + ex);
            }
            // 是否避免返回的頁面被緩存,默認爲 false
            // 避免緩存是經過添加「Cache-Control=no-store」實現的
            prepareResponse(ex, response);

            // 該方法交由子類擴展
            ModelAndView result = doResolveException(request, response, handler, ex);
            if (result != null) {
                // 日誌記錄
                logException(ex, request);
            }
            return result;
        } else {
            return null;
        }
    }

    protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {
        if (handler != null) {
            // 首先判斷 mappedHandlers是否包含此 Handler,由 setMappedHandlers方法指定
            if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
                return true;
            }
            // 再判斷 mappedHandlerClasses是否指定了該 Handler類型,由 setMappedHandlerClasses方法指定
            if (this.mappedHandlerClasses != null) {
                for (Class<?> handlerClass : this.mappedHandlerClasses) {
                    if (handlerClass.isInstance(handler)) {
                        return true;
                    }
                }
            }
        }
        // 若是不指定,默認 mappedHandlers和 mappedHandlerClasses都爲 null,所以返回 true
        return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
    }
}

    AbstractHandlerExceptionResolver 是框架層面全部異常解析器的公共抽象父類,實現了resolveException 方法,首先判斷下是否支持該 Handler 的異常處理,若是不指定 mappedHandlers、mappedHandlerClasses,默認支持所有 Handler 的異常處理。框架

    具體異常處理的邏輯以抽象方法(doResolveException)的形式交由子類實現。jsp

   @ExceptionHandler 本來的支持類爲 AnnotationMethodHandlerExceptionResolver,該類在 Spring 3.2 版本後聲明廢棄,代替者爲 ExceptionHandlerExceptionResolveride

    AbstractHandlerMethodExceptionResolver 做爲 ExceptionHandlerExceptionResolver 的抽象父類,實現 doResolveException。this

public abstract class AbstractHandlerMethodExceptionResolver extends AbstractHandlerExceptionResolver {

    @Override
    protected final ModelAndView doResolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        // 抽象方法:子類實現
        return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
    }
}

    這個方法這是作了次參數的向下轉型,進而調用 doResolveHandlerMethodException ,這個方法由 ExceptionHandlerExceptionResolver 實現。(這裏又看到了 「HandlerMethod 」 的身影)

public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
        implements ApplicationContextAware, InitializingBean {

    @Override
    protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
                  HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {

        // 跟請求處理相同的套路,一樣使用 ServletInvocableHandlerMethod
        ServletInvocableHandlerMethod exceptionHandlerMethod = 
                                  getExceptionHandlerMethod(handlerMethod, exception);
        if (exceptionHandlerMethod == null) {
            return null;
        }

        // 入參和返回解析器設置
        exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();

        try {
            if (logger.isDebugEnabled()) {
                logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
            }
            Throwable cause = exception.getCause();
            if (cause != null) {
                // 調用邏輯見請求處理章節
                exceptionHandlerMethod.invokeAndHandle(
                           webRequest, mavContainer, exception, cause, handlerMethod);
            } else {
                exceptionHandlerMethod.invokeAndHandle(
                           webRequest, mavContainer, exception, handlerMethod);
            }
        } catch (Throwable invocationEx) {
            // 有可能在調用 Handler處理時拋出異常,例如斷言判空之類的
            if (invocationEx != exception && logger.isWarnEnabled()) {
                logger.warn("Failed to invoke @ExceptionHandler method: " + 
                             exceptionHandlerMethod, invocationEx);
            }
            return null;
        }

        // 判斷請求是否已在 Handler中徹底處理
        if (mavContainer.isRequestHandled()) {
            return new ModelAndView();
        } else {
            // 將 Handler處理的結果封裝成 ModelAndView返回
            ModelMap model = mavContainer.getModel();
            HttpStatus status = mavContainer.getStatus();
            ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
            mav.setViewName(mavContainer.getViewName());
            if (!mavContainer.isViewReference()) {
                mav.setView((View) mavContainer.getView());
            }
            if (model instanceof RedirectAttributes) {
                Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
                request = webRequest.getNativeRequest(HttpServletRequest.class);
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
            return mav;
        }
    }

}

    剛纔提到的異常處理方式2、方式三,說到底也是一次 Method 的調用過程:就像請求經過映射關係找到對應的 HandlerMethod 同樣,ExceptionHandlerExceptionResolver 經過定義的異常類型來找到對應的 HandlerMethod,進而調用 invokeAndHandle 來反射調用(裏面包含的參數解析、反射調用等,在以前的章節已分析過)。

    這裏重點來分析下 getExceptionHandlerMethod 尋找 HandlerMethod 的邏輯。

protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
        // 首先獲取拋出異常點(業務處理方法)所在類的類型
        Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);

        if (handlerMethod != null) {
            // 先從緩存中取,若是沒有則建立一個並放入緩存
            ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
            if (resolver == null) {
                resolver = new ExceptionHandlerMethodResolver(handlerType);
                this.exceptionHandlerCache.put(handlerType, resolver);
            }
            // 關注此方法:解析出處理該異常的方法
            Method method = resolver.resolveMethod(exception);
            // 若是不爲 null,說明拋異常的方法所在類下,正好有一個被 @ExceptionHandler註解標識的方法對應處理該異常
            if (method != null) {
                // 包裝一個 ServletInvocableHandlerMethod返回
                return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
            }
        }

        // 若是對應類下沒有能力捕獲到該異常,就遍歷全部 @ControllerAdvice標識的類下,指定的 @ExceptionHandler
        for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
            // 判斷是否支持傳入類型的加強
            // 若是 @ControllerAdvice沒有指定 basePackages、basePackageClasses、annotations、assignableTypes,則返回 true,即全局加強
            // 若是指定了上述的順序,逐個篩選,知足其一就返回 true
            if (entry.getKey().isApplicableToBeanType(handlerType)) {
                ExceptionHandlerMethodResolver resolver = entry.getValue();
                // 關注此方法:解析出處理該異常的方法
                Method method = resolver.resolveMethod(exception);
                if (method != null) {
                    // 包裝一個 ServletInvocableHandlerMethod返回
                    return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
                }
            }
        }

        return null;
    }

    從源碼能夠看出,首先會從異常拋出所在類中找處理方法(方式二),沒有才會到全局加強中找(方式三)。這裏使用到的緩存 exceptionHandlerAdviceCache 初始化藉助了生命週期接口實現  InitializingBean .afterPropertiesSet,該方法調用了 initExceptionHandlerAdviceCache 將全局被 @ControllerAdvice 標識的類實例放入 exceptionHandlerAdviceCache

    接着來看下二者共同使用的 ExceptionHandlerMethodResolver 是如何經過異常解析出 Method,上面代碼經過 new ExceptionHandlerMethodResolver(handlerType) 建立了該類的實例,該構造器將會初始化異常和處理方法的映射關係,源碼以下:

public class ExceptionHandlerMethodResolver {

    // 存放異常和處理方法的映射關係
    private final Map<Class<? extends Throwable>, Method> mappedMethods =
            new ConcurrentHashMap<Class<? extends Throwable>, Method>(16);

    public ExceptionHandlerMethodResolver(Class<?> handlerType) {
        // 遍歷 Handler下的全部方法,找到被 @ExceptionHandler標識的方法
        for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
            // 遍歷方法指定處理的異常類型(Throwable類型)
            for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
                // 註冊異常和對應處理方法的映射關係
                addExceptionMapping(exceptionType, method);
            }
        }
    }

    private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
        List<Class<? extends Throwable>> result = new ArrayList<Class<? extends Throwable>>();
        // 找方法上 @ExceptionHandler 註解聲明的異常類型
        detectAnnotationExceptionMappings(method, result);
        // 註解未指定,則會將方法入參爲 Throwable類型填充 result
        if (result.isEmpty()) {
            for (Class<?> paramType : method.getParameterTypes()) {
                if (Throwable.class.isAssignableFrom(paramType)) {
                    result.add((Class<? extends Throwable>) paramType);
                }
            }
        }
        // 若是註解和方法都沒有指定異常類型,拋出異常
        if (result.isEmpty()) {
            throw new IllegalStateException("No exception types mapped to " + method);
        }
        return result;
    }

    private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
        // 關聯關係
        Method oldMethod = this.mappedMethods.put(exceptionType, method);
        // 防止一個異常對應多個處理方法
        if (oldMethod != null && !oldMethod.equals(method)) {
            throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                    exceptionType + "]: {" + oldMethod + ", " + method + "}");
        }
    }
}

    須要注意,這裏的 detectExceptionMappings 若是註解指定了異常類型,就不會再考慮方法入參的異常類型了。

    映射關係創建以後,繼續看解析邏輯:

public Method resolveMethod(Exception exception) {
        Method method = resolveMethodByExceptionType(exception.getClass());
        if (method == null) {
            Throwable cause = exception.getCause();
            if (cause != null) {
                method = resolveMethodByExceptionType(cause.getClass());
            }
        }
        return method;
    }

    public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
        // 首先從緩存中獲取
        Method method = this.exceptionLookupCache.get(exceptionType);
        if (method == null) {
            // 緩存中沒有,則從 mappedMethods中獲取
            method = getMappedMethod(exceptionType);
            // 放入緩存
            this.exceptionLookupCache.put(exceptionType, (method != null ? method : NO_METHOD_FOUND));
        }
        return (method != NO_METHOD_FOUND ? method : null);
    }

    這裏可能有人會疑問,以前已經創建了異常和處理方法的映射關係,爲何又維護了一個 exceptionLookupCache 呢?

    咱們來假象一種場景:某方法拋出的異常 A,A 繼承自類型 B。咱們針對異常 A 和 B 分別定義了處理方法,那麼究竟哪一個方法會處理異常 A?

     所以就有一個篩選邏輯,邏輯見 getMappedMethod 方法,使用 ExceptionDepthComparator 比較器進行最優處理方法挑選(即挑選聲明異常越具體的方法)。這裏不作展開分析。

    到此爲止,異常處理已經分析完成。

 

視圖跳轉

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 使用 initLocaleResolver初始化的 LocaleResolver進行國際化處理(默認:AcceptHeaderLocaleResolver)
        // 肯定請求的語言環境並將其應用於響應
        Locale locale = (this.localeResolver != null ? 
                    this.localeResolver.resolveLocale(request) : request.getLocale());
        response.setLocale(locale);

        View view;
        String viewName = mv.getViewName();
        if (viewName != null) {
            // 遍歷註冊的 ViewResolver,調用resolveViewName解析 viewName爲 View
            view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
            if (view == null) {
                throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
                        "' in servlet with name '" + getServletName() + "'");
            }
        } else {
            // 若是沒有指定 viewName,則直接獲取視圖
            view = mv.getView();
            if (view == null) {
                throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
                        "View object in servlet with name '" + getServletName() + "'");
            }
        }

        ....// 省略日誌

        try {
            if (mv.getStatus() != null) {
                // 響應碼設置
                response.setStatus(mv.getStatus().value());
            }
            // 調用 View.render
            view.render(mv.getModelInternal(), request, response);
        } catch (Exception ex) {
            ....// 省略日誌
            throw ex;
        }
    }

    涉及到了國際化處理(LocaleResolver),視圖解析ViewResolver)。其中 resolveViewName 遍歷了全部註冊的 ViewResolver,將 viewName 轉換爲 View。以 UrlBasedViewResolver 爲例,它會將指定的 prefix、suffix 先後綴與 viewName 拼接後建立 View。

public abstract class AbstractView extends WebApplicationObjectSupport implements View, BeanNameAware {

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

        ....// 省略日誌

        // 將 View實例配置中的固定屬性和 Model中的動態屬性合併到一塊兒
        Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
        prepareResponse(request, response);
        // 子類擴展
        renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
    }

}

    renderMergedOutputModel 方法由子類實現。

public class InternalResourceView extends AbstractUrlBasedView {

	@Override
	protected void renderMergedOutputModel(
			Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

		// 調用 setAttribute將 model中屬性設置到 request中
		exposeModelAsRequestAttributes(model, request);

		exposeHelpers(request);

		// 肯定請求分派器的路徑
		String dispatcherPath = prepareForRendering(request, response);

		// 獲取請求的 RequestDispatcher
		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!");
		}

		// 判斷當前  response是否已經提交
		if (useInclude(request, response)) {
			response.setContentType(getContentType());
			....// 省略日誌
			
			// 調用 RequestDispatcher.include,見 <jsp:include>
			// 該方法調用後,原 Servlet仍對請求有主導權,並將新的資源包含到當前響應當中
			rd.include(request, response);
		}

		else {
			....// 省略日誌
			
			// 調用 RequestDispatcher.forward
			// 該方法將請求直接轉發給其餘 Servlet處理
			rd.forward(request, response);
		}
	}
}

    這裏以 InternalResourceView 爲例,JSP 就是經過這種方式實現的。JSP 全稱 Java Servlet Page,底層就是將所寫的 JSP 頁面編譯成 Servlet 字節碼文件,這裏的轉發也就是將請求轉發給這些對應的 Servlet 。

    固然除了 InternalResourceView ,還有像 MappingJackson2JsonView(支持 @JsonView 註解)等。以後的邏輯就交由 Servlet 容器(像 Tomcat)的實現了。

 

總結

    到此爲止,Spring 從容器啓動到一次請求處理就所有解析完畢了。

相關文章
相關標籤/搜索