当前位置: 首页 > news >正文

Spring MVC 请求处理过程。你这样回答保证通过面试!

前言
SpringMVC 请求处理相信大家都很熟悉了,本篇主要是基于 SpringMVC 处理请求的流程来阅读并调试源码,以及解决几个仅靠流程图无法解释的问题。

关于 Spring MVC 的流程思维导图分享给大家:

Spring 系列的学习笔记和面试题,包含 spring 面试题、spring cloud 面试题、spring boot 面试题、spring 教程笔记、spring boot 教程笔记、最新阿里巴巴开发手册(63 页 PDF 总结)、2020 年 Java 面试手册。一共整理了 1184 页 PDF 文档。

获取这份 1184 页 PDF 文档的 spring 全家桶资料。

本篇使用的 Spring 版本为 5.2.2.RELEASE

九大组件
SpringMVC 几乎所有的功能都由九大组件来完成,所以明白九大组件的作用,对于学习 SpringMVC 来说非常重要。

/** 文件上传解析器 /
private MultipartResolver multipartResolver;
/
* 区域解析器,用于国际化 /
private LocaleResolver localeResolver;
/
* 主题解析器 /
private ThemeResolver themeResolver;
/
* Handler映射信息 /
private List handlerMappings;
/
* Handler适配器*/
private List handlerAdapters;
/** Handler执行异常解析器 /
private List handlerExceptionResolvers;
/
* 请求到视图的转换器 /
private RequestToViewNameTranslator viewNameTranslator;
/
* SpringMVC允许重定向时携带参数,存在session中,用完就销毁,所以叫FlashMap /
private FlashMapManager flashMapManager;
/
* 视图解析器 */
private List viewResolvers;
HandlerMapping:Handler 映射信息,根据请求携带的 url 信息查找处理器(Handler)。每个请求都需要对应的 Handler 处理。

HandlerAdapter:Handler 适配器,SpringMVC 没有直接调用处理器(Handler),而是通过 HandlerAdapter 来调用,主要是为了统一 Handler 的调用方式

ViewResolver:视图解析器,用来将字符串类型的视图名称解析为 View 类型的视图。ViewResolver 需要找到渲染所用的模板和所用的技术(也就是视图的类型)进行渲染,具体的渲染过程则交由不同的视图自己完成。

MultipartResolver:文件上传解析器,主要用来处理文件上传请求

HandlerExceptionResolver:Handler 执行异常解析器,用来对异常进行统一处理

RequestToViewNameTranslator:请求到视图的转换器

LocaleResolver:区域解析器,用于支持国际化

FlashMapManager:SpringMVC 允许重定向时携带参数,存在 session 中,用完就销毁,所以叫 FlashMap

ThemeResolver:主题解析器,用于支持不同的主题

九大组件中最重的的前三个,HandlerMapping、HandlerAdapter 和 ViewResolver,因为这是阅读源码时,避不开的三个组件。

我把 Spring MVC 相关的技术文章整理成了 PDF,老规矩,关注微信公众号 Java 后端 回复 666 下载。

调试准备
搭建一个基本的 Spring web 项目即可

Controller 部分

@Controllerpublic class IndexController
{
@RequestMapping(“/index/home”)
public String home(String id, Student student,
@RequestParam(“code”) String code)
{
System.out.println(student.getName());
return “index”;
}
@ResponseBody
@RequestMapping(“/index/list”)
public String list()
{
return “success”;
}
}
Entity 部分

public class Student { private String name; private Integer gender; // getter、setter}
还是那句话,Spring 源码非常庞大,不能只见树木不见森林,需要有针对性的阅读,所以本篇只需要关注主体流程即可。

核心方法
我们都知道,SpringMVC 有一个用来分发请求的前端控制器 DispatcherServlet,其中用来处理请求的方法就是 doService,该方法定义如下

doService

/** * Exposes the DispatcherServlet-specific request attributes and delegates to
{
@link
#doDispatch} * for the actual dispatching. */
@Overrideprotected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception
{ logRequest(request);
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include. Map<String, Object> attributesSnapshot = null;
if (WebUtils.isIncludeRequest(request))
{
attributesSnapshot = new HashMap<>();
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements())
{
String attrName = (String) attrNames.nextElement();
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX))
{
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
// Make framework objects available to handlers and view objects. request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
if (this.flashMapManager != null)
{
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null)
{
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap)); }
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap()); request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
try
{
// 真正执行的方法 doDispatch(request, response);
}
finally
{
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted())
{
// Restore the original attribute snapshot, in case of an include. if (attributesSnapshot != null)
{
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
doDispatch
doDispatch 是 doService 中真正用来处理请求的方法

/** * 实际处理请求的方法 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception
{
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try
{
ModelAndView mv = null;
Exception dispatchException = null;
try
{
// 校验是否是文件上传请求 processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
// 为当前请求找到一个合适的处理器(Handler)
// 返回值是一个HandlerExecutionChain,也就是处理器执行链 mappedHandler = getHandler(processedRequest);
if (mappedHandler == null)
{
noHandlerFound(processedRequest, response); return; }
// Determine handler adapter for the current request.
// 根据HandlerExecutionChain携带的Handler找到合适的HandlerAdapter HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
// 处理GET请求的缓存 String method = request.getMethod();
boolean isGet = “GET”.equals(method);
if (isGet || “HEAD”.equals(method))
{
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet)
{
return;
}
}
// 执行拦截器的preHandle方法 if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; }
// Actually invoke the handler.
// 利用HandlerAdapter来执行Handler里对应的处理方法 mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted())
{
return;
}
// 如果没有设置视图,则应用默认的视图名 applyDefaultViewName(processedRequest, mv);
// 执行拦截器的postHandle方法 mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex)
{
dispatchException = ex;
}
catch (Throwable err)
{
// As of 4.3, we’re processing Errors thrown from handler methods as well,
// making them available for
@ExceptionHandler methods and other scenarios. dispatchException = new NestedServletException(“Handler dispatch failed”, err);
}
// 根据ModelAndView对象解析视图 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex)
{
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err)
{
triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException(“Handler processing failed”, err));
}

finally
{
if (asyncManager.isConcurrentHandlingStarted())
{
// Instead of postHandle and afterCompletion if (mappedHandler != null)
{
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else
{
// Clean up any resources used by a multipart request. if (multipartRequestParsed)
{
cleanupMultipart(processedRequest);
}
}
}
}
该方法就是 SpringMVC 处理请求的整体流程,其中涉及到几个重要的方法。

getHandler
该方法定义如下

/** * Return the HandlerExecutionChain for this request. * 为这个request返回一个HandlerExecutionChain */
@Nullableprotected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception
{
if (this.handlerMappings != null)
{
for (HandlerMapping mapping : this.handlerMappings)
{
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null)
{
return handler;
}
}
}
return null;}
调试信息如下

根据调试信息可以看出,getHandler 方法主要是从 List handlerMappings 集合中遍历查找一个合适的处理器(Handler),返回的结果是一个 HandlerExecutionChain。然后再根据 HandlerExecutionChain 里携带的 Handler 去获取 HandlerAdapter。

getHandlerAdapter
getHandlerAdapter 方法定义如下

/**

  • Return the HandlerAdapter for this handler object. *
    @param handler the handler object to find an adapter for *
    @throws ServletException if no HandlerAdapter can be found for the handler. This is a fatal error.
    */
    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException
    {
    if (this.handlerAdapters != null)
    {
    for (HandlerAdapter adapter : this.handlerAdapters)
    {
    if (adapter.supports(handler))
    {
    return adapter;
    }
    }
    }
    throw new ServletException(“No adapter for handler [” + handler + “]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler”);
    }
    调试信息如下

同样 getHandlerAdapter 方法主要是从 List handlerAdapters 集合中遍历查找一个合适的处理器适配器(HandlerAdapter),返回的结果是一个 HandlerAdapter。

可以看到此处 HandlerAdapter 真正的实现类是 RequestMappingHandlerAdapter。

processDispatchResult
processDispatchResult 方法主要根据方法执行完成后封装的 ModelAndView,转发到对应页面,定义如下

/**

  • Handle the result of handler selection and handler invocation, which is * either a ModelAndView or an Exception to be resolved to a ModelAndView. */
    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    @Nullable HandlerExecutionChain mappedHandler,
    @Nullable ModelAndView mv,
    @Nullable 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);
    }
    }
    // Did the handler return a view to render? if (mv != null && !mv.wasCleared())
    {
    // 主要调用该方法渲染视图 render(mv, request, response);
    if (errorView)
    {
    WebUtils.clearErrorRequestAttributes(request);
    }
    }
    else
    {
    if (logger.isTraceEnabled())
    {
    logger.trace(“No view rendering, null ModelAndView returned.”);
    }
    } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted())
    {
    // Concurrent handling started during a forward return;
    }
    if (mappedHandler != null)
    {
    // Exception (if any) is already handled… mappedHandler.triggerAfterCompletion(request, response, null);
    }
    }
    render
    render 方法定义如下

/**

  • Render the given ModelAndView. *

    This is the last stage in handling a request. It may involve resolving the view by name. * @param mv the ModelAndView to render *
    @param request current HTTP servlet request *
    @param response current HTTP servlet response *
    @throws ServletException if view is missing or cannot be resolved *
    @throws Exception if there’s a problem rendering the view */
    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception
    {
    // Determine locale for request and apply it to the response. Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);
    View view;
    String viewName = mv.getViewName();
    if (viewName != null)
    {
    // We need to resolve the view name.
    // 根据给定的视图名称,解析获取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
    {
    // No need to lookup: the ModelAndView object contains the actual View object. 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() + “'”);
    }
    }
    // Delegate to the View object for rendering. if (logger.isTraceEnabled())
    {
    logger.trace(“Rendering view [” + view + "] ");
    }
    try
    {
    if (mv.getStatus() != null)
    {
    response.setStatus(mv.getStatus().value());
    }
    view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex)
    {
    if (logger.isDebugEnabled())
    {
    logger.debug(“Error rendering view [” + view + “]”, ex);
    }
    throw ex;
    }
    }
    resolveViewName
    resolveViewName 方法定义如下

@Nullableprotected 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;
}
调试信息如下

根据调试信息可以看到真正解析视图的 ViewResolver 的是 InternalResourceViewResolver 类,也就是我们经常配置的一项类型


至此我们就得到了 SpringMVC 处理请求的完整逻辑

SpringMVC 处理请求的整个流程已经梳理清楚了。

但是,有两个重要的问题没有解决,那就是:参数绑定和返回值处理。

因为在编写 Controller 里面的方法的时候,各种类型的参数都有,SpringMVC 是怎么处理不同类型的参数的呢? > SpringMVC 处理请求完成后,一定会返回 ModelAndView 吗,如果加了 @ResponseBody 注解呢?

参数绑定
在整个流程中,还有一个最重要的方法,那就是真正执行 handler 的方法,参数的绑定和返回值的处理都在这个方法里,也就是

// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
handle
handle 方法的作用是根据请求参数,执行真正的处理方法,并且返回合适的 ModelAndView 对象,也有可能返回 null。该方法定义如下 在 AbstractHandlerMethodAdapter 类中

/** * This implementation expects the handler to be an
{
@link HandlerMethod}. */
@Override
@Nullablepublic final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception
{
return handleInternal(request, response, (HandlerMethod) handler);
}
可以看到这个方法实现只有一行代码

handleInternal
继续深入 handleInternal 方法

@Overrideprotected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
{
ModelAndView mav;
// 校验指定的请求以获取受支持的方法类型(GET、POST等)和所需的session checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required. if (this.synchronizeOnSession)
{
HttpSession session = request.getSession(false);
if (session != null)
{
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex)
{
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else
{
// No HttpSession available -> no mutex necessary mav = invokeHandlerMethod(request, response, handlerMethod); } }
else
{
// No synchronization on session demanded at all…
// 真正执行handler的方法 mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL))
{
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes())
{
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else { prepareResponse(response);
}
}
return mav;
}
invokeHandlerMethod

继续深入 invokeHandlerMethod 方法

/**

  • Invoke the
    {
    @link RequestMapping} handler method preparing a
    {
    @link ModelAndView} * if view resolution is required. * 执行
    @RequestMapping标注的handler方法,如果需要解析视图就准备一个ModelAndView */
    @Nullableprotected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
    {
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try
    {
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
    // HandlerMethod接口封装执行方法的信息,提供对方法参数,方法返回值,方法注释等的便捷访问。 ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null)
    {
    invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null)
    {
    invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
    // ModelAndViewContainer可以看做ModelAndView的上下文容器,关联着Model和View的信息 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
    modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
    AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
    asyncWebRequest.setTimeout(this.asyncRequestTimeout);
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(as…
    【这里想说,因为自己也走了很多弯路过来的,所以才下定决心整理,收集过程虽不易,但想到能帮助到一部分自学java 的人,心里也是甜的!有需要的伙伴请点㊦方】↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓

相关文章:

  • 两台电脑mysql数据迁移,各版本mysql迁移(亲测)
  • MD5退出历史舞台你知道吗?
  • 使用nw.js将web项目打包为exe软件(xp版本)
  • 我,在日本开密室逃脱,钱还没赚,人进“橘子”了……
  • “池化技术” - 深度剖释底层内存管理细节,明晰“池化技术”内存管理技术
  • 【网站架构】Tomcat长时间运行崩溃?Tomcat调优、集群
  • Android Studio中Touch监听器的使用
  • Docker | redis安装及测试
  • zabbix监控部署keepalived高可用
  • 单机Mysql的演进
  • 智工教育:公务员考试这些知识点你会背了吗?
  • 动态路由协议(一)
  • nodejs--fastify-url构建以及路由前缀和新增用户
  • 关系型数据库RDS基本简介
  • 20分钟学会git基本操作,创建远程仓库
  • 【前端学习】-粗谈选择器
  • 2017 年终总结 —— 在路上
  • CentOS 7 修改主机名
  • Create React App 使用
  • ES6简单总结(搭配简单的讲解和小案例)
  • Git 使用集
  • leetcode-27. Remove Element
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • Python十分钟制作属于你自己的个性logo
  • scala基础语法(二)
  • Spring Boot MyBatis配置多种数据库
  • Spring核心 Bean的高级装配
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • ubuntu 下nginx安装 并支持https协议
  • ⭐ Unity 开发bug —— 打包后shader失效或者bug (我这里用Shader做两张图片的合并发现了问题)
  • 多线程事务回滚
  • 关于字符编码你应该知道的事情
  • 极限编程 (Extreme Programming) - 发布计划 (Release Planning)
  • 京东美团研发面经
  • 前端自动化解决方案
  • 学习笔记:对象,原型和继承(1)
  • 选择阿里云数据库HBase版十大理由
  • # 20155222 2016-2017-2 《Java程序设计》第5周学习总结
  • #includecmath
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (13):Silverlight 2 数据与通信之WebRequest
  • (173)FPGA约束:单周期时序分析或默认时序分析
  • (30)数组元素和与数字和的绝对差
  • (C语言版)链表(三)——实现双向链表创建、删除、插入、释放内存等简单操作...
  • (Matlab)使用竞争神经网络实现数据聚类
  • (vue)el-checkbox 实现展示区分 label 和 value(展示值与选中获取值需不同)
  • (解决办法)ASP.NET导出Excel,打开时提示“您尝试打开文件'XXX.xls'的格式与文件扩展名指定文件不一致
  • (九)One-Wire总线-DS18B20
  • (四)TensorRT | 基于 GPU 端的 Python 推理
  • (学习总结)STM32CubeMX HAL库 学习笔记撰写心得
  • (一)硬件制作--从零开始自制linux掌上电脑(F1C200S) <嵌入式项目>
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)程序员疫苗:代码注入
  • (转载)在C#用WM_COPYDATA消息来实现两个进程之间传递数据
  • .NET C# 使用 iText 生成PDF