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 的人,心里也是甜的!有需要的伙伴请点㊦方】↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓