专栏首页求道当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?SpringMVC视图处理器与视图篇章【终章】

当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?SpringMVC视图处理器与视图篇章【终章】

| 好看请赞,养成习惯

  • 我曾踏足山巅,也曾跌入低谷,二者都使我受益良多!
  • I've been to the top, and I've fallen to the bottom, and I've learned a lot from both!

本篇文章依旧是基于上篇文章的基础做的描述,请先观看

  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程序狗(javacxg),作者:皇甫嗷嗷叫

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-06-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 设计模式之单例模式

    小弟最近在研究设计模式,准备边学边发博客,与众多大佬们交流学习,希望各位能够指出不足之处(废话不多说了,直接开花)** **

    止术
  • 你好MyBatis 中高级篇

    在看到这个需求时,如果你不了解MyBatis的动态sql的话,恐怕第一想到的就是,后台进行一系列的判断,判断执行那个方法和对应的具体sql吧!但是MyBatis...

    止术
  • RedLock究竟是不是Redis分布式锁分布式环境下的银弹?

    在这个技术不断更新迭代的情况下,分布式这个概念,在企业中的权重越来越高!谈及分布式时,不可避免一定会提到分布式锁,现阶段分布式锁的实现方式主流的有三种实现方式,...

    止术
  • JDK1.8源码(七)——java.util.HashMap 类

      本篇博客我们来介绍在 JDK1.8 中 HashMap 的源码实现,这也是最常用的一个集合。但是在介绍 HashMap 之前,我们先介绍什么是 Hash表。...

    IT可乐
  • JavaScript 类型的那些事

    JavaScript的类型判断是前端工程师们每天代码中必备的部分,每天肯定会写上个很多遍if (a === 'xxx')或if (typeof a === 'o...

    疯狂的技术宅
  • Android如何调用系统相机拍照

    本文实例为大家分享了Android调用系统相机拍照的具体代码,供大家参考,具体内容如下

    砸漏
  • 秋招季,用Python分析深圳程序员工资有多高?

    多图预警、多图预警、多图预警。秋招季,毕业也多,跳槽也多。我们的职业发展还是要顺应市场需求,那么各门编程语言在深圳的需求怎么呢?工资待遇怎么样呢?zone 在上...

    数据森麟
  • 回忆:我技术生涯中的那些“惊悚”瞬间,谢老板当年的不“杀”之恩

    中秋前,圈内一则 “顺丰运维工程师因误删库被开除” 的消息在圈内炸锅了。虽说程序员群体并不八婆,但都纷纷就此事件抛出深藏内心的酸爽感。

    吃草的罗汉
  • 终于搞懂了Keras中multiloss的对应关系介绍

    补充知识:keras服务器用fit_generator跑的代码,loss,acc曲线图的保存

    砸漏
  • Python操作符

    py3study

扫码关注云+社区

领取腾讯云代金券