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 从容器启动到一次请求处理就所有解析完毕了。

相关文章
相关标签/搜索