博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringMVC源码解析(五)——视图处理
阅读量:6153 次
发布时间:2019-06-21

本文共 18517 字,大约阅读时间需要 61 分钟。

hot3.png

前言

    本篇将分析一次请求从接收到处理的最终环节——视图处理,也是 SpringMVC 源码解析的最后一节。将涉及异常处理视图转发两部分。

 

源码解读

    承接上篇,来看 “processDispatchResult” 的实现。

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“未涉及到的异常响应页面)

    方式一可能比较局限,一个类只能针对某种类型异常的处理;方式二、方式三比较方便,共同点都是借助了 @ExceptionHandler 注解,value 可以指定异常类型数组;方式四一般用于异常是自定义的场景;方式五是配置方式的,可以通过基于 xml 文件配置,也可以通过 SpringBoot 的基于代码配置。

    这里仅分析下来方式二方式三的实现原理,这也是比较常用的两种方式:

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)的形式交由子类实现。

   @ExceptionHandler 原本的支持类为 AnnotationMethodHandlerExceptionResolver,该类在 Spring 3.2 版本后声明废弃,代替者为 ExceptionHandlerExceptionResolver

    AbstractHandlerMethodExceptionResolver 作为 ExceptionHandlerExceptionResolver 的抽象父类,实现 doResolveException。

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
flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } }}

    刚才提到的异常处理方式二、方式三,说到底也是一次 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
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
, Method> mappedMethods = new ConcurrentHashMap
, Method>(16); public ExceptionHandlerMethodResolver(Class
handlerType) { // 遍历 Handler下的所有方法,找到被 @ExceptionHandler标识的方法 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { // 遍历方法指定处理的异常类型(Throwable类型) for (Class
exceptionType : detectExceptionMappings(method)) { // 注册异常和对应处理方法的映射关系 addExceptionMapping(exceptionType, method); } } } private List
> detectExceptionMappings(Method method) { List
> result = new ArrayList
>(); // 找方法上 @ExceptionHandler 注解声明的异常类型 detectAnnotationExceptionMappings(method, result); // 注解未指定,则会将方法入参为 Throwable类型填充 result if (result.isEmpty()) { for (Class
paramType : method.getParameterTypes()) { if (Throwable.class.isAssignableFrom(paramType)) { result.add((Class
) paramType); } } } // 如果注解和方法都没有指定异常类型,抛出异常 if (result.isEmpty()) { throw new IllegalStateException("No exception types mapped to " + method); } return result; } private void addExceptionMapping(Class
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
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
model, HttpServletRequest request, HttpServletResponse response) throws Exception { ....// 省略日志 // 将 View实例配置中的固定属性和 Model中的动态属性合并到一起 Map
mergedModel = createMergedOutputModel(model, request, response); prepareResponse(request, response); // 子类扩展 renderMergedOutputModel(mergedModel, getRequestToExpose(request), response); }}

    renderMergedOutputModel 方法由子类实现。

public class InternalResourceView extends AbstractUrlBasedView {	@Override	protected void renderMergedOutputModel(			Map
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,见
// 该方法调用后,原 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 从容器启动到一次请求处理就全部解析完毕了。

转载于:https://my.oschina.net/marvelcode/blog/1842044

你可能感兴趣的文章
linux运维人员的成功面试总结案例分享
查看>>
Windows DHCP Server基于MAC地址过滤客户端请求实现IP地址的分配
查看>>
命令查询每个文件文件数
查看>>
《跟阿铭学Linux》第8章 文档的压缩与打包:课后习题与答案
查看>>
RAC表决磁盘管理和维护
查看>>
Apache通过mod_php5支持PHP
查看>>
发布一个TCP 吞吐性能测试小工具
查看>>
java学习:jdbc连接示例
查看>>
PHP执行批量mysql语句
查看>>
Extjs4.1.x 框架搭建 采用Application动态按需加载MVC各模块
查看>>
Silverlight 如何手动打包xap
查看>>
建筑电气暖通给排水协作流程
查看>>
JavaScript面向对象编程深入分析(2)
查看>>
linux 编码转换
查看>>
POJ-2287 Tian Ji -- The Horse Racing 贪心规则在动态规划中的应用 Or 纯贪心
查看>>
Windows8/Silverlight/WPF/WP7/HTML5周学习导读(1月7日-1月14日)
查看>>
关于C#导出 文本文件
查看>>
使用native 查询时,对特殊字符的处理。
查看>>
maclean liu的oracle学习经历--长篇连载
查看>>
ECSHOP调用指定分类的文章列表
查看>>