Spring MVC 处理请求的过程
涉及到的组件:
-
DispatcherServlet
-
HandlerMapping
-
HandlerAdapter
关于这三个组件的详细内容,下面会单独介绍
处理请求过程
Step 1 : 经过过滤器链
目的:请求的过滤和预处理
常见的过滤器:强制指定请求和响应的编码格式 / 将post请求转化为别的请求(因为有的浏览器只支持Get/Post)/ 初始化RequestContext上下文,把当前请求和响应放入Spring MVC的请求上下文中。
Step 2 :到达Servlet组件
到达javax.servlet.http.HttpServlet,这是一个抽象类,执行的时候肯定是一个具体类,这个具体类就是 DispatcherServlet.
- 进入
HttpServlet中的service方法
(1)将原始的Servlet请求转化为基于Http的Servlet请求和响应
类型转换: ServletRequest->HttpServletRequest
然后调用重载的service方法(同一个类中),传入的方法参数就是上述转化后的req,rep
- 同一个类中重载的service方法
根据请求类型(Get,Post,Delete)执行do*()方法,如doPost,doGet等*
分发片段:
需要注意的的是,Spring MVC 作为对原生代码的增强,自然复用了这段代码,继承HttpServlet的同时(
FrameworkServlet类,依旧是抽象类),并重写了doGet()、doPost()、..方法,在这些方法里面,代码都如下:
Step 3殊途同归:处理请求
定义在FrameworkServlet中的processRequest()方法,所有类型的请求都最终调用它。
- 完整方法
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//-------------构建本地化的上下文,请求上下文,异步管理器----------------------//
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//请求的处理和当前线程是异步关系
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
initContextHolders(request, localeContext, requestAttributes);
//-------------------------执行业务------------------------------------//
try {
doService(request, response);
}
//--------------------------异常捕获-----------------------------------//
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}
finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
Step 4 :执行服务
step3代码段中的doService()方法是FrameworkServlet类中的抽象方法,它被子类DispatchServlet重写,宣布请求正式被DispatchServlet接手
- 重写后的doService()方法:
@Override
protected 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);
}
}
}
}
- 它做了什么?
-
保存请求属性快照,以便后续恢复(针对include请求类型)
-
将Spring MVC 的Web应用上下文放入请求属性(request.setAttributes())
-
将本地化解析器放入请求属性
-
主题解析器放入请求属性
-
执行核心分发方法:doDispatch(request, response) 方法
3.解释
(1)Web应用上下文
为了执行的时候可以使用上下文中的组件
(2)本地化解析器
获取当前请求所关联的地理地区,可以针对性的返回对应的本地化视图(如针对不同国家返回不同语言)
(3)主题解析器
可以用来实现前端页面使用的切换主题功能(如返回不同的css)
在这里停一下,Step1 ~ Step4 收到请求,并完成原始Servlet和Spring MVC 的对接
对接完毕后,进入doDispatch方法,会执行所有的请求处理操作,包括请求的分发,响应的处理等核心操作。这也是整个Spring MVC中最复杂的部分
在正式开始剖析之前,看一眼doDispatch的流程图:
对应的源代码如下,与上图中复杂的流程相比,整个doDispatch显得并不是很长,因为每个方法都经历了独立的封装,你可以找到图中箭头和代码的对应关系
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.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
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;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
applyDefaultViewName(processedRequest, mv);
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);
}
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);
}
}
}
}
总结一下:
Step5 :(分发前)预处理多块请求
针对多块请求这个特殊情况下的预处理,主要为一些简单判断:判断请求是否为多块请求,判断方式是content-type是不是multipart开头的,如果是,执行多块请求解析
注:multipart请求在传文件的时候会经常用到
Step6 : 查找handler
Spring MVC 把
handler的查找与handler的执行分离
查找该请求对应的处理器执行链,查找结果被封装到一个 HandlerExecutionChain对象
查找方式(遇见HandlerMapping)
遍历全部处理器映射(this.handlerMappings),这个list是DispatchServlet的一个属性,
getHandler()会循环遍历它
HandlerMapping是今天三个最重要的组件之二,我们之所以看到上面的代码逻辑很简单,只需要一个for循环遍历handlerMappings找到handler,是因为核心逻辑都在HandlerMapping中
现在我们关注的是Dispatch的完整流程,至于HandlerMapping如何进行匹配的,我们会讲回来,现在我们继续抓住主线。
Step 7 :获取可以执行Handler的适配器(Adapter)
前面提到过,再重复一下这个重要概念:Spring MVC 对于Handler的查找与执行是分离的。在第七步之前我们已经找到了这个Handler。
根据Handler不同,需要使用不同的适配器去执行该处理器,这正是处理适配器 HandlerAdapter的作用
对应的doDispatch中的片段:
接下来一个小问题,为什么要适配器?
把请求参数适配为处理器需要的参数,把处理器的返回值适配为ModelAndView
Step 8 :处理HTTP缓存功能
HTTP缓存是HTTP协议标准中的定义,用于提高HTTP的效率。像JS,CSS,图片这种文件,下次浏览器发送相同请求的时候,会携带本地缓存的资源时间作为请求头传递给服务端。比如通过If-Modified-Since携带本地资源的最近一次修改时间,如果服务器判断缓存有效,就返回304,表示可以直接使用缓存中的资源。
额外注意一下HTTP中只有GET和HEAD支持缓存,这段代码如下;
Step 9 :执行前置拦截器链
请求处理器执行链中封装了“拦截器链”,用于在执行请求逻辑前执行,执行请求逻辑后添加后置处理,还可以拦截所有的请求处理,在所有处理完成时或发生异常时添加完成后操作
该过程会正序遍历执行所有拦截器。在拦截器链中,任何拦截器的prehandler方法返回false,均会中断后续执行,这种场景多用于用户登录校验
Step 10 :执行Handler
交给adapter执行整个流程中最重要的方法(真是千呼万唤始出来)
Step 11 :返回值视图名处理
Step 12 :执行后置拦截器链
该过程会倒序执行所有拦截器。
前置拦截器和后置拦截器的执行逻辑对比:
前置是正序:
后置是倒序执行:
Step 13 :处理返回值和响应
在拿到处理适配器的处理结果ModelAndView后,接下来就可以根据这个返回值向响应中添加数据了。
步骤:
先看看整个处理过程有没有造成异常:
如果有:
封装异常视图(把异常封装成一个ModelAndView)
如果没有:
第一步:看看处理结果ModelAndView是不是为空
如果为空,打印日志,不为空执行第二步
第二步:
render(mv, request, response);
- 注:这里的render就是诸如
Thymeleaf模版引擎起作用的地方