基于最新Spring 5.x,详细介绍了Spring MVC 请求的执行流程源码,给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图。
我正在参与CSDN《新程序员》有奖征文,活动地址:https://marketing.csdn.net/p/52c37904f6e1b69dc392234fff425442。
此前,我们学习了Spring MVC项目启动初始化过程中的部分重要源码,Spring MVC项目启动之后,便能够接受请求、处理请求。
此前,我们已经学习了Spring MVC的请求执行流程,但实际上在Spring MVC请求执行流程过程中,要做的事儿有很多,比图CORS配置、参数转换等等,现在让我们从源码的角度再一次深入了解Spring MVC 请求的执行流程。
在文章的最后我们也给出了更加详细的Spring MVC请求执行流程步骤总结,以及详细的执行流程图,嫌弃源码太长的小伙伴可以直接跳到末尾。
下面的源码版本基于5.2.8.RELEASE
。
Spring MVC 初始化源码(1)—ContextLoaderListener与父上下文容器的初始化
Spring MVC 初始化源码(2)—DispatcherServlet与子容器的初始化以及MVC组件的初始化【一万字】
Spring MVC 初始化源码(3)—
Spring MVC 初始化源码(4)—@RequestMapping注解的源码解析
Spring MVC 请求执行流程的源码深度解析【两万字】
Spring MVC是对原始Servlet的封装,虽然我们在开发过程中不会再接触到Servlet级别的API,但是我们知道Spring MVC中有一个核心的Servlet实现,那就是DispatcherServlet,它作为核心控制器,用于接收任何的请求并将请求转发给对应的处理组件。因此,Spring MVC的请求处理入口仍然可以从DispatcherServlet中找到。
DispatcherServlet的uml类图如下:
可以看到,从它间接的继承了HttpServlet,因此Spring MVC的请求源码入口同样是HttpServlet#service()
方法!
这个service方法中并没有做太多的事情,主要是将ServletRequest和ServletResponse
强转转换为HttpServletRequest和HttpServletResponse
,最后会调用另一个service
方法,该方法才是真正的核心处理请求的方法。
HttpServlet自己的service
方法源码如下。其内部时一系列的以do开头的模版方法,回顾一下,在早期原始的Servlet项目
中,我们所要开发的就是这些doGet、doPost
方法:
并且,如果我们足够心细,我们能发现FrameworkServlet直接重写了HttpServlet的整个service方法
,此前的原始Servlet开发
的我们都是开发的do开头的方法
,比如doGet、doPost
,但是Spring MVC将整个service方法
都重写了,我们一起来看看。
FrameworkServlet#service
方法可以看作Spring MVC一次请求的处理入口方法。
FrameworkServlet重写父类HttpServlet的service方法
的原因在注释上说的很明白了,就是为了支持PATCH
请求。
重写的方法的逻辑比较简单,首先解析出当前请求的方法,如果是PATCH
方法或者没有请求方法,则直接调用processRequest
方法处理该请求,否则,对于其他请求方法,则还是会调用父类HttpServlet的service来处理
,最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法
来处理。
/**
* FrameworkServlet的方法
*
* 重写父类HttpServlet的service实现用以拦截PATCH请求。
*/
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取当前请求的请求方法,可能是:GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
//如果是PATCH请求,或者请求方法为null,则另外处理
//PATCH方法是新引入的,是对PUT方法的补充,用来对已知资源进行局部更新,目前用的比较少
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
//直接调用processRequest方法处理
processRequest(request, response);
} else {
//对于其他请求方法,则还是会调用父类HttpServlet的service来处理
//最终,对于不同的请求方法,还是会调用各自对应的do开头的模版方法来处理
super.service(request, response);
}
}
很明显,do开头的一系列方法也都由DispatcherServlet及其父类
帮我们都实现好了,因为在使用Spring MVC框架的时候我们并没有编写这些底层方法的实现。实际上,do开头的一系列模版方法,也是由FrameworkServlet
帮我们实现的,并且我们能够发现它实现的这些方法最终都会指向processRequest方法
。
从这里就能看出processRequest
方法的重要性了,继续向下看!
processRequest
方法位于FrameworkServlet
中,提供了处理请求方法的骨架实现,并且留出了一系列模版方法
让子类去实现自己的逻辑,这是模版方法模式的应用。
该方法会将本次请求的LocaleContext和RequestAttributes
绑定到LocaleContextHolder和RequestContextHolder
的线程本地变量属性中,这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response
等对象,比如:HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
,注意需要在当前请求线程中才能获取到。
请求处理完毕之后,无论成功与否
,都会调用publishRequestHandledEvent
方法发布一个ServletRequestHandledEvent
事件,表示该请求处理完毕,我们可以监听
该事件,并做出不同的处理。
该方法中的核心处理请求的方法就是doService
模版方法,该方法留给子类比如DispatcherServlet
实现。
/**
* FrameworkServlet的方法
*
* 处理此请求,发布事件,无论结果如何。
*/
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//当前时间毫秒
long startTime = System.currentTimeMillis();
//失败原因(异常)
Throwable failureCause = null;
//获取之前可能存在的LocaleContext,可能在Filter中设置的
//LocaleContext中可以获取当前的语言环境Locale
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
//根据当前request构建LocaleContext,就是new 一个SimpleLocaleContext
LocaleContext localeContext = buildLocaleContext(request);
//获取之前可能存在的RequestAttributes,可能在Filter中设置的
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
//根据当前request、response、previousAttributes构建新的ServletRequestAttributes
//如果此前的previousAttributes不为null,那么就还是使用这个对象
//否则就通过request和response直接new 一个ServletRequestAttributes,因此RequestAttributes中可以获取request和response
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
//获取异步请求管理器,新建一个WebAsyncManager对象,并且通过setAttribute存入request的属性中
//属性名为org.springframework.web.context.request.async.WebAsyncManager.WEB_ASYNC_MANAGER
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
//注册RequestBindingInterceptor这个拦截器,该拦截器可以初始化或者重置LocaleContext和RequestAttributes
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
//初始化LocaleContext和RequestAttributes绑定到LocaleContextHolder和RequestContextHolder的线程本地变量属性中
//这样在当前请求处理过程中就可以在其他地方直接获取本次请求的request、response等对象,比如:
//HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
initContextHolders(request, localeContext, requestAttributes);
try {
/*
* 处理请求的核心模版方法,留给子类比如DispatcherServlet实现
*/
doService(request, response);
} catch (ServletException | IOException ex) {
//获取处理请求过程中抛出的异常
failureCause = ex;
throw ex;
} catch (Throwable ex) {
//获取处理请求过程中抛出的异常
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
} finally {
//最终,将LocaleContext和RequestAttributes从LocaleContextHolder和RequestContextHolder的线程本地变量属性中解除绑定
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
//发出请求已完成的信号。
//将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
/*
* 发布一个事件,表示该请求处理完毕,无论成功与否
* 我们可以监听该事件,并做出不同的处理
*/
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
doService
是处理请求的核心模版方法
,该方法留给子类比如DispatcherServlet
实现。
DispatcherServlet的doService方法
主要是将DispatcherServlet的一些属性
放置在当前请求的属性中,方便后续直接从request
中获取,并委托doDispatch
方法进行实际请求处理
,因此实际的真正请求处理还得看doDispatch
方法(是不是觉得调用层次很深呢?)。
//DispatcherServlet的属性
/**
* 包含请求执行完毕后执行请求属性的清理吗,默认清理
*/
private boolean cleanupAfterInclude = true;
/**
* DispatcherServlet的默认策略属性以其开头的公共前缀。
*/
private static final String DEFAULT_STRATEGIES_PREFIX = "org.springframework.web.servlet";
/**
* 保存当前的Web应用程序上下文的请求属性
*/
public static final String WEB_APPLICATION_CONTEXT_ATTRIBUTE = DispatcherServlet.class.getName() + ".CONTEXT";
/**
* 保存当前的LocaleResolver的请求属性,可通过视图检索。
*/
public static final String LOCALE_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".LOCALE_RESOLVER";
/**
* 保存当前的ThemeResolver的请求属性,可由视图检索。
*/
public static final String THEME_RESOLVER_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_RESOLVER";
/**
* 保存当前的ThemeSource的请求属性,可通过视图检索。
*/
public static final String THEME_SOURCE_ATTRIBUTE = DispatcherServlet.class.getName() + ".THEME_SOURCE";
/**
* DispatcherServlet的方法
*
* 公开特定于DispatcherServlet的请求属性,并委托doDispatch进行实际请求处理。
*/
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
//如果是包含请求(include,即请求包含),那么保存request 属性快照,以便能够在包含请求处理完毕之后恢复原始属性
//也就是说,在包含请求处理过程中设置的request 属性将不会被保留
Map<String, Object> attributesSnapshot = null;
//判断是否是请求包含,尝试从request的属性中获取名为javax.servlet.include.request_uri的属性
//如果存在该属性,那么就是包含请求,否则就不是
if (WebUtils.isIncludeRequest(request)) {
attributesSnapshot = new HashMap<>();
//获取属性名
Enumeration<?> attrNames = request.getAttributeNames();
while (attrNames.hasMoreElements()) {
String attrName = (String) attrNames.nextElement();
//默认将所有属性存入快照map中
if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
attributesSnapshot.put(attrName, request.getAttribute(attrName));
}
}
}
//将一些对象存入request的属性中,方便handler和view中使用
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());
//flashMap管理器,默认为SessionFlashMapManager
//FlashMap可用于将属性从一个请求传递到另一个请求,通常是用在重定向中。
if (this.flashMapManager != null) {
//查找由与当前请求匹配的先前请求保存的FlashMap,将其从基础存储中删除,还删除其他过期的FlashMap实例。
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()) {
//如果是包含请求,那么还原原始属性快照。
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
在processRequest方法的finally块的最后,将会通过当前DispatcherServlet关联的webApplicationContext发布一个ServletRequestHandledEvent事件,表示该请求处理完毕,无论成功与否。
ServletRequestHandledEvent中包含了本次请求的各种信息,我们可以监听该事件,并做出不同的处理。
//FrameworkServlet的属性
/**
* 我们是否应该在每个请求的末尾发布ServletRequestHandledEvent?
* 默认是应该的,但可以通过该参数关闭
*/
private boolean publishEvents = true;
/**
* 此Servlet关联的WebApplicationContext,在此前的initServletBean方法中被初始化
*/
@Nullable
private WebApplicationContext webApplicationContext;
/**
1. FrameworkServlet的方法
2.
3. 发布一个事件,表示该请求处理完毕,无论成功与否
4. 5. @param request 当前request
6. @param response 当前response
7. @param startTime 请求处理开始时间戳毫秒
8. @param failureCause 失败原因(抛出的异常,可能为null)
*/
private void publishRequestHandledEvent(HttpServletRequest request, HttpServletResponse response,
long startTime, @Nullable Throwable failureCause) {
//如果允许发布事件并且关联的IoC容器不为null,那么无论我们是否成功,都发布一个事件。
if (this.publishEvents && this.webApplicationContext != null) {
//计算请求处理花费的时间
long processingTime = System.currentTimeMillis() - startTime;
//通过IoC容器发布ServletRequestHandledEvent事件,包含各种请求信息
//这样我们就可以使用Spring的事件监听机制来监听这个事件进而来监听这个请求了
this.webApplicationContext.publishEvent(
//this:事件源,当前DispatcherServlet对象
new ServletRequestHandledEvent(this,
//请求路径、ip地址
request.getRequestURI(), request.getRemoteAddr(),
//请求方法、ServletName
request.getMethod(), getServletConfig().getServletName(),
//SessionId、username
WebUtils.getSessionId(request), getUsernameForRequest(request),
//请求处理时间、失败原因、响应状态码
processingTime, failureCause, response.getStatus()));
}
}
将请求实际分派给对应的handler并且调用不同的组件进行请求处理。该方法的逻辑实际上就是Spring MVC请求处理的主体流程。
大概逻辑为:
getHandler
方法确定当前请求的处理器——handler。getHandlerAdapter
方法根据handler确定当前请求的处理器适配器——HandlerAdapter。applyPreHandle
方法,顺序应用拦截器链中的此前找到的所有拦截器的PreHandle
预处理方法。全部通过则执行后续步骤,不通过则倒序执行已通过的拦截器的afterCompletion
方法,随后直接返回。handle
方法,通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象。applyPostHandle
方法,倒序应用拦截器链中的此前找到的所有拦截器的postHandle
后处理方法。processDispatchResult
方法,处理执行handler的结果。主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图。triggerAfterCompletion
方法,倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion
处理方法,表示请求处理完毕。/**
* DispatcherServlet的方法
*
* 将请求实际分派给对应的handler进行处理。
*
* 该handler将通过依次应用servlet的HandlerMappings来获得,并且通过查询Servlet的
* handlerAdapters来查找支持该handler的第一个HandlerAdapter,从而获得HandlerAdapter。
* 所有HTTP方法都由该方法处理,由HandlerAdapters或handler本身来决定可接受的方法。
*
* @param request 当前 HTTP request
* @param response 当前 HTTP response
*/
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
//processedRequest初始化为当前request
HttpServletRequest processedRequest = request;
//已匹配的handler,也就是Handler执行链
HandlerExecutionChain mappedHandler = null;
//是否是已解析的多部件请求,即文件上传请求
boolean multipartRequestParsed = false;
//异步请求管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
//模型和视图对象,handler执行的返回结果,内部包含了model和view对象
ModelAndView mv = null;
//记录异常
Exception dispatchException = null;
try {
/*
* 将请求使用多部分解析器转换为多部分请求,如果未设置多部分解析器或者当前请求不是多部分请求,则返回原始请求。
* 对于CommonsMultipartResolver,它底层走的是ApacheCommons FileUpload的文件上传逻辑,因此需要引入依赖
* 它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求
* 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest对象
*
* 对于StandardServletMultipartResolver,它是走的Servlet 3.0多部分请求解析的逻辑,不需要引入额外依赖
* 它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求
* 则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest对象
*/
processedRequest = checkMultipart(request);
//判断是否是多部件请求,即文件上传请求
multipartRequestParsed = (processedRequest != request);
/*
* 1 确定当前请求的处理器——handler
*
* 通过遍历handlerMappings,依次调用每个HandlerMapping的getHandler方法获取HandlerExecutionChain对象
* 只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找
* 如果在所有的handlerMapping中都没找到,则返回null。
*
* 对于RequestMappingHandlerMapping,他返回的handler就是HandlerMethod
*/
mappedHandler = getHandler(processedRequest);
//如果没有找到任何handler,那么执行noHandlerFound的逻辑
if (mappedHandler == null) {
//通常返回404响应码或者抛出NoHandlerFoundException异常
noHandlerFound(processedRequest, response);
//直接返回,doDispatch方法结束
return;
}
/*
* 2 根据handler确定当前请求的处理器适配器——HandlerAdapter
*
* 通过遍历handlerAdapters,依次调用每个HandlerAdapter的supports方法
* 只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找
* 如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException。
*
* 对于HandlerMethod,它的适配器就是RequestMappingHandlerAdapter
*/
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
/*
* 如果handler支持,则处理last-modified头
*/
String method = request.getMethod();
boolean isGet = "GET".equals(method);
//如果是GET或者HEAD请求
if (isGet || "HEAD".equals(method)) {
//获取last-modified头信息,这是对应的文档的最近一次更新时间
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
//如果没有任何改变并且是GET请求,则直接返回
//这是对于不经常改变的文档的缓存机制的支持,如果此前获取过某个文档,并且此次访问时该文档仍然没有改变,则服务器不会再次渲染该文档
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
/*
* 3 顺序应用拦截器链中的此前找到的所有拦截器的PreHandle预处理方法
* 只要有一个拦截器不通过,那么该请求就不会继续处理,而是直接返回
*/
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
/*
* 4 通过HandlerAdapter使用给定的handler实际处理请求,返回一个ModelAndView结果对象
*
* 不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验等等步骤
* 对于HandlerMethod来说,就会尝试执行对应的@ReuqestMapping方法,也就是业务逻辑
*/
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//如果存在mv并且没有view,则设置view为默认的viewName
applyDefaultViewName(processedRequest, mv);
/*
* handler正常处理完毕
* 5 倒序应用拦截器链中的此前找到的所有拦截器的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);
}
/*
* 6 处理执行handler返回的结果
* 主要目的是处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图,最后倒序应用拦截器的afterCompletion方法
*/
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
/*
* 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕
*
* 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行
*/
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
/*
* 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法,表示请求处理完毕
*
* 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行
*/
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 {
//清理多部分请求使用的所有资源。
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
使用多部分解析器将请求转换为多部分请求,如果未设置多部分解析器,则只需使用现有请求。多部分解析器来自于此Servlet关联的IoC容器bean,beanName指定为multipartResolver。
对于CommonsMultipartResolver
,它底层走的是ApacheCommons FileUpload的文件上传逻辑
,因此需要引入依赖,它会检查如果是POST请求并且content-type是"multipart/"前缀,则算作文件上传请求,则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)被解析为一个DefaultMultipartHttpServletRequest
对象。
对于StandardServletMultipartResolver
,它是走的Servlet 3.0多部分请求解析的逻辑
,不需要引入额外依赖,它会检查请求的content-type如果是"multipart/"前缀,则算作文件上传请求,则原始HttpServletRequest(实际上是tomcat中的RequestFacade对象)对象被解析为一个StandardMultipartHttpServletRequest
对象。
//DispatcherServlet的属性
/**
* 此servlet使用的MultipartResolver
* 来自于此Servlet关联的IoC容器bean,beanName指定为multipartResolver
*/
@Nullable
private MultipartResolver multipartResolver;
/**
* DispatcherServlet的方法
*
* 使用多部分解析器将请求转换为多部分请求,如果未设置多部分解析器,则只需使用现有请求。
*
* @param request 当前 HTTP request
* @return 已处理的请求(如果需要,则使用多部分请求包装)
*/
protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
//如果设置了multipartResolver并且此请求是多部分请求,即文件上传请求
if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
//如果该请求已经是MultipartHttpServletRequest 那仅仅打印日志
//如果我们设置了MultipartFilter这个过滤器,那么在请求到达Servlet之前就会被判断是否是多部分请求
//如果是,则会被被解析为MultipartHttpServletRequest,MultipartFilter默认使用StandardServletMultipartResolver来解析
if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
if (request.getDispatcherType().equals(DispatcherType.REQUEST)) {
logger.trace("Request already resolved to MultipartHttpServletRequest, e.g. by MultipartFilter");
}
} else if (hasMultipartException(request)) {
logger.debug("Multipart resolution previously failed for current request - " +
"skipping re-resolution for undisturbed error rendering");
} else {
try {
//调用多部分解析器的resolveMultipart方法将原始HttpServletRequest请求解析为MultipartHttpServletRequest的实现请求
//对于CommonsMultipartResolver,会创建一个DefaultMultipartHttpServletRequest并返回,其内部包装了原始的的请求
//对于StandardServletMultipartResolver则会创建一个StandardMultipartHttpServletRequest并返回,其内部包装了原始的的请求
return this.multipartResolver.resolveMultipart(request);
} catch (MultipartException ex) {
if (request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) != null) {
logger.debug("Multipart resolution failed for error dispatch", ex);
// Keep processing error dispatch with regular request handle below
} else {
throw ex;
}
}
}
}
//返回原始请求
return request;
}
按顺序尝试调用所有的HandlerMapping
,返回此请求的HandlerExecutionChain
,如果找不到handler,则返回null
。
该方法就是常见的Spring MVC执行流程的第一步
了,即“通过HandlerMapping处理器映射器找到对应的Handler处理器,返回一个HandlerExecutionChain执行链对象,该对象包含一个拦截器链,链的尾部就是当前的handler处理器,对于基于@RequestMapping注解方法注册的控制器来说,handler就是HandlerMethod
”。
SpringMVC默认加载三个请求处理映射类:RequestMappingHandlerMapping、SimpleUrlHandlerMapping和BeanNameUrlHandlerMapping
。如果配置了标签,或者
@EnableWebMvc
注解,则不会加载默认配置,但至少会加载两个:RequestMappingHandlerMapping和BeanNameUrlHandlerMapping
,还会加上我们自定义配置的HandlerMapping
的bean。
//DispatcherServlet的属性
/**
* 此servlet使用的HandlerMappings列表
*/
@Nullable
private List<HandlerMapping> handlerMappings;
/**
1. DispatcherServlet的方法
2.
3. 按顺序尝试调用所有的HandlerMapping,返回此请求的HandlerExecutionChain。
4. 5. @param request 当前 HTTP request
6. @return HandlerExecutionChain;如果找不到handler,则返回null
*/
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
//遍历所有的HandlerMapping,依次调用getHandler方法尝试获取HandlerExecutionChain
//如果某个返回结果不为null,则返回该返回值,不会继续向后查找
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
Spring MVC通过调用HandlerMapping#getHandler
方法来获取该请求对应的HandlerExecutionChain
。
通常RequestMappingHandlerMapping
排在handlerMappings
集合的首位,将首先被调用。我们主要讲解RequestMappingHandlerMapping的getHandler
方法实现。
不同的HandlerMapping
实现,它的getHandler
方法也不一样。我们能够很容易的猜到Spring对此使用了模板方法
模式,有一个HandlerMapping的抽象实现类,该类实现了getHandler接口并提供了骨架实现,然后有各个子类提供细节的实现,这个抽象类就是AbstractHandlerMapping
。
该方法的主要逻辑为:
getHandlerInternal
方法尝试获取匹配的handler处理器,对于RequestMappingHandlerMapping
就是返回HandlerMethod
。BeanNameUrlHandlerMapping
用到。getHandlerExecutionChain
方法,为给定的handler构建一个HandlerExecutionChain
对象,其中还包括所有适用的拦截器形成的拦截器链属性interceptorList
。CorsConfigurationSource
接口或者具有全局CORS配置
(比如通过
标签或者WebConfig#addCorsMappings
),或者说当前handler属于HandlerMethod
,并且存在对于当前HandlerMethod的局部CORS配置
(比如通过@CrossOrigin
注解),或者说当前请求是CORS预检请求
。满足以上三个条件之一
,那么就可以对当前请求进行CORS配置
,以用于后续CORS处理
。HandlerExecutionChain
。/**
* AbstractHandlerMapping的属性
* 全局的CORS配置
*/
@Nullable
private CorsConfigurationSource corsConfigurationSource;
/**
* AbstractHandlerMapping的方法
*
* 查找给定请求的handler,如果未找到特定的handler,则退回到使用默认handler。
*
* @param request 当前 HTTP request
* @return 相应的handler处理程序实例或默认handler处理程序
*/
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
/*
* 1 查找给定请求的handler处理程序,如果未找到特定请求,则返回null,随后将使用默认handler.
*
* 对于CORS的预检请求,此方法从"Access-Control-Request-Method"头部字段中获取实际请求所使用的HTTP方法
* 从"Access-Control-Request-Headers"头部字段中获取实际请求所携带的自定义首部字段与此方法的CORS配置进行匹配
*
* 该方法是一个抽象方法,留给子类实现。对于RequestMappingHandlerMapping,则会尝试返回HandlerMethod
* 过程中会匹配@ReuqestMapping设置的各种条件,比如path、Method、params、headers、consumes、produces、patterns……
*/
Object handler = getHandlerInternal(request);
if (handler == null) {
//如未未找到特定请求,则使用默认handler。这个默认handler是我们自己配置的,默认也是为null
handler = getDefaultHandler();
}
//通过上面的查找,如果handler还是为null,那么返回null
if (handler == null) {
return null;
}
//判断handler是Bean名称或已解析的handler实例
if (handler instanceof String) {
//如果是beanName,那么就从IoC容器中查找对应的handler实例
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
/*
* 2 为给定的handler构建一个HandlerExecutionChain对象,其中还包括所有适用的拦截器形成的拦截器链interceptorList
*
* 默认实现使用给定的handler,公共拦截器以及与当前请求URL匹配的任何MappedInterceptors来构建标准的HandlerExecutionChain。
*/
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
} else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}
/*
* 3 如果当前handler实现了CorsConfigurationSource接口或者具有全局CORS配置(比如通过标签或者WebConfig#addCorsMappings),
* 或者说当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解)
* 或者说当前请求是CORS预检请求
* 满足以上三个条件之一,那么就可以对当前请求进行CORS配置,以用于后续CORS处理
*/
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
//获取全局CORS配置,比如基于标签配置
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
//获取针对当前handler的CORS配置,比如类和方法上的@CrossOrigin注解配置
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
//CORS配置合并,对于单个字段的配置,方法上的@CrossOrigin注解优先级更高,方法和类上的@CrossOrigin注解的值将组合(java中是list集合)
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
/*
* 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,并且使用此前的
* chain中的拦截器链构建一个新的HandlerExecutionChain对象返回
*
* 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,
* 用于后面执行拦截器时处理CORS请求,最后还是返回当前chain对象
*/
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
查找给定请求的handler处理程序,如果未找到特定请求,则返回null,随后将使用默认handler。
对于CORS的预检请求,此方法从"Access-Control-Request-Method
“头部字段中获取实际请求所使用的HTTP方法,从”Access-Control-Request-Headers
"头部字段中获取实际请求所携带的自定义首部字段,将会获取此方法的CORS配置进行匹配。
该方法是一个抽象方法,留给子类实现,并且比较复杂,需要详细讲解,我们主要看RequestMappingHandlerMapping
的实现。RequestMappingHandlerMapping的getHandlerInternal
在它的父类RequestMappingInfoHandlerMapping
和父类的父类AbstractHandlerMethodMapping
中均有实现。
RequestMappingHandlerMapping
的uml类图如下:
/**
* HandlerMapping中的常量
*
* HttpServletRequest属性的名称,该属性包含适用于映射处理程序的可产生MediaType的集合。
*/
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* RequestMappingInfoHandlerMapping的方法
*
* @param request 当前 request
* @return 内部的handler处理器对象,实际上就是HandlerMethod
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//移除该属性
request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
try {
//调用父类AbstractHandlerMethodMapping的实现方法
return super.getHandlerInternal(request);
} finally {
ProducesRequestCondition.clearMediaTypesAttribute(request);
}
}
/**
* HttpServletRequest属性的名称,该属性包含用于查找匹配handler的路径
*/
String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";
/**
* AbstractHandlerMethodMapping的方法
*
* 查找给定请求的HandlerMethod
*/
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
//获取请求路径,将会通过该路径匹配handler
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//设置到当前request的属性中
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
//调用lookupHandlerMethod()方法根据给定的path和request尝试查找HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
} finally {
this.mappingRegistry.releaseReadLock();
}
}
该方法将查找查找当前请求的最佳匹配handler的HandlerMethod
,如果找到多个
匹配项,则选择最佳
匹配项。将会匹配path、Method、params、headers、consumes、produces、patterns、customCondition
等等条件。
如果是匹配的CORS预检请求
,将返回一个特殊的空HandlerMethod
。
/**
* AbstractHandlerMethodMapping的属性
*
* 用于CORS预检请求的特殊HandlerMethod
* 其内部的bean是一个EmptyHandler,方法也是该bean的handle方法
* 执行该方法将抛出异常
*/
private static final HandlerMethod PREFLIGHT_AMBIGUOUS_MATCH =
new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"));
/**
* HandlerMapping的属性
* 最佳匹配的handler的HttpServletRequest属性的名称。
*/
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
/**
* AbstractHandlerMethodMapping的方法
*
* 查找当前请求的最佳匹配handler的方法,如果找到多个匹配项,则选择最佳匹配项。
*
* @param lookupPath 当前servlet映射内的映射查找路径
* @param request 当前 request
* @return 最佳匹配的HandlerMethod;如果不匹配,则返回null
*/
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
//匹配的结果集合
List<Match> matches = new ArrayList<>();
/*
* 根据lookupPath取注册表中查找所有匹配的RequestMappingInfo,这里并没有模式匹配,而是精确匹配
* 这里只匹配路径,不会匹配其他的条件,因此可能找到多个RequestMappingInfo,比如它们之间使用请求方法来区分时
*/
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
//对找到的精确路径匹配的RequestMappingInfo继续应用其他属性匹配
if (directPathMatches != null) {
//比如Method、params、headers、consumes、produces、patterns、customCondition等等
//如果这些条件都匹配,那么对应的RequestMappingInfo和handlerMethod会被包装为一个Match对象存入matches集合
addMatchingMappings(directPathMatches, matches, request);
}
/*
* 如果没有任何精确路径匹配,那么遍历所有的映射,再次尝试匹配
* 这里面会尝试模式匹配
*/
if (matches.isEmpty()) {
//该方法中再次匹配时实际上会使用模式匹配
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
/*
* 如果找到了至少一个匹配,那么查找最佳匹配
*/
if (!matches.isEmpty()) {
//默认第一个是最佳匹配
Match bestMatch = matches.get(0);
//如果总匹配数大于1个,那么查找最佳匹配
if (matches.size() > 1) {
//匹配比较器
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
//比较并排序,最终,最佳匹配将位于集合首位
matches.sort(comparator);
//获取第一个元素,就是最佳匹配的元素
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
//如果是CORS的预检请求,那么固定返回一个空的HandlerMethod实例
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
//第二匹配
Match secondBestMatch = matches.get(1);
//如果第二匹配和第一匹配的比较结果是相等的,这说明没找到最佳匹配,或者说有两个最佳匹配
//那么将抛出异常
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
//将最佳匹配的HandlerMethod的存入到当前request的属性中公开
//属性名为org.springframework.web.servlet.HandlerMapping.bestMatchingHandler
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
//在请求中继续公开lookupPath、URI模板变量,矩阵变量和可使用的媒体类型,将它们作为当前请求的属性
handleMatch(bestMatch.mapping, lookupPath, request);
//返回最佳匹配的handlerMethod
return bestMatch.handlerMethod;
} else {
//如果没有匹配任何一个,那么会放宽条件再次尝试全部匹配
//如果存在部分匹配的handlerMethod,那么会尝试使用该handlerMethod,否则将抛出异常或者返回null
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
如果当前handler实现了CorsConfigurationSource
接口或者具有全局CORS配置(比如通过
标签或者WebConfig#addCorsMappings
),或者说当前handler属于HandlerMethod
,并且存在对于当前HandlerMethod的局部CORS配置
(比如通过@CrossOrigin
注解),或者说当前请求是CORS预检请求
。满足以上三个条件之一,那么就可以对当前请求进行CORS配置
,以用于后续CORS处理
。
将会对全局CORS配置和局部CORS配置合并
,对于只能接受单个值的属性(如allowCredentials和 maxAge
),本地值将覆盖全局值,对于其他接受多个值的属性,比如origins
,则全局和本地的CORS配置值会组合在一起(java中是list集合)!
最后会调用getCorsHandlerExecutionChain
配置CORS。
该方法判断是否具有全局CORS配置或者对于当前HandlerMethod的局部CORS配置。如果有的话,那么当前请求就可以执行CORS配置处理。
/**
* AbstractHandlerMethodMapping的方法
* 是否具有适用于当前handler的CORS配置
*
* @param handler 当前handler
* @return true——有
*/
@Override
protected boolean hasCorsConfigurationSource(Object handler) {
//调用父类的同名方法校验
return super.hasCorsConfigurationSource(handler) ||
//当前handler属于HandlerMethod,并且存在对于当前HandlerMethod的局部CORS配置(比如通过@CrossOrigin注解),那么返回true
(handler instanceof HandlerMethod && this.mappingRegistry.getCorsConfiguration((HandlerMethod) handler) != null);
}
/**
1. AbstractHandlerMapping的方法
2.
3. 是否具有适用于当前handler的CORS配置
*/
protected boolean hasCorsConfigurationSource(Object handler) {
if (handler instanceof HandlerExecutionChain) {
handler = ((HandlerExecutionChain) handler).getHandler();
}
//如果handler属于CorsConfigurationSource类型或者具有全局的CORS配置,那么返回true
return (handler instanceof CorsConfigurationSource || this.corsConfigurationSource != null);
}
该方法将会判断:
CORS预检请求
,则使用当前的config配置构建一个PreFlightHandler
,并且使用此前的chain中的拦截器链构建一个新的HandlerExecutionChain
对象返回。后面执行handler时,实际上是进行CORS校验
。否则
,在当前chain的拦截器链的头部添加一个CorsInterceptor
,用于后面执行拦截器时校验CORS配置
,最后还是返回当前chain
对象。/**
* AbstractHandlerMapping的方法
*
* 获取具有CORS配置的HandlerExecutionChain
*
* @param request 当前request
* @param chain 当前的handler chain
* @param config 适用的CORS配置(可能为null)
* @return 配置了CORS的HandlerExecutionChain
*/
protected HandlerExecutionChain getCorsHandlerExecutionChain(HttpServletRequest request,
HandlerExecutionChain chain, @Nullable CorsConfiguration config) {
/*
* 如果是CORS预检请求,则使用当前的config配置构建一个PreFlightHandler,
* 并且使用此前的chain中的拦截器链构建一个新的HandlerExecutionChain对象返回。
*/
if (CorsUtils.isPreFlightRequest(request)) {
HandlerInterceptor[] interceptors = chain.getInterceptors();
chain = new HandlerExecutionChain(new AbstractHandlerMapping.PreFlightHandler(config), interceptors);
} else {
/*
* 否则,在当前chain的拦截器链的头部添加一个CorsInterceptor,用于后面执行拦截器时处理CORS请求,最后还是返回当前chain对象。
*/
chain.addInterceptor(0, new AbstractHandlerMapping.CorsInterceptor(config));
}
return chain;
}
找不到handler的时候,则调用该方法,通常是设置404
的HTTP响应状态码,但可能抛出NoHandlerFoundException
异常,需要手动设置DispatcherServlet的throwExceptionIfNoHandlerFound属性为true
。
/**
* DispatcherServlet的属性
*
* 如果未找到处理该请求的handler,则抛出NoHandlerFoundException,默认不抛出
*/
private boolean throwExceptionIfNoHandlerFound = false;
/**
* DispatcherServlet的方法
*
* 找不到handler->设置适当的HTTP响应状态码。
*
* @param request 当前 HTTP request
* @param response 当前 HTTP response
*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {
if (pageNotFoundLogger.isWarnEnabled()) {
pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));
}
//如果该属性为true,则直接抛出NoHandlerFoundException异常
if (this.throwExceptionIfNoHandlerFound) {
throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),
new ServletServerHttpRequest(request).getHeaders());
} else {
//否则,直接设置404相应状态吗,这是常见逻辑
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
}
该方法根据上面找到的handler获取对应的处理器适配器——HandlerAdapter
。
通过遍历handlerAdapters
,依次调用每个HandlerAdapter的supports
方法,只要有一个
HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会
继续向后查找,如果在所有的HandlerAdapter中都没找到
,则直接抛出ServletException
。
/**
* DispatcherServlet的属性
*
* 此servlet使用的HandlerAdapter列表
*/
@Nullable
private List<HandlerAdapter> handlerAdapters;
/**
* DispatcherServlet的方法
*
* 返回此handler对象的HandlerAdapter。
*
* @param handler 用来查找适配器的handler对象,比如HandlerMethod
*/
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
//遍历所有的HandlerAdapter,依次调用supports方法尝试判断是否支持
//如果某个返回结果为true,则说明该adapter支持该handler,则返回该adapter,不继续向后查找
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
//如果找不到处理程序的HandlerAdapter,则直接抛出ServletException异常
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
不同的handler
需要不同的HandlerAdapter
来适配,对于HandlerMethod
,它的适配器就是RequestMappingHandlerAdapter
。
RequestMappingHandlerAdapter
将会被默认配置,它的uml类图如下:
它的supports方法是由它的父类AbstractHandlerMethodAdapter
实现的,非常的简单,只要此handler是HandlerMethod类型那么就返回true,表示可以处理
。
/**
* AbstractHandlerMethodAdapter的方法
*
* 此实现期望handler的类型为HandlerMethod。
*
* @param handler 要检查的handler实例
* @return 此适配器是否可以适应给定的handler
*/
@Override
public final boolean supports(Object handler) {
//如果此handler是HandlerMethod类型并且,supportsInternal方法返回true(默认就返回true)
//则此此适配器可以适应给定的handler,将返回true
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
/**
* RequestMappingHandlerAdapter的方法
*
* 始终返回true
*/
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
在实际执行请求之前,应用拦截器链中的此前找到的所有HandlerInterceptor
拦截器的PreHandle
预处理方法,这里的拦截器是Spring MVC
的拦截器。如果执行链应该继续下一个拦截器或处理程序本身,则返回true
。否则,DispatcherServlet假定此拦截器已经处理了响应本身,随后将直接返回,不会
继续执行doDispatch
方法。
我们此前知道,如果存在CORS配置并且不是CORS预检请求
,将会拦截器链的头部添加一个CorsInterceptor
拦截器,此时就会执行该拦截器以校验CORS
,如果不通过,那么preHandle返回false
,后续handler方法也就不再执行,避免了由于CORS不通过但是业务代码执行的问题!而对于预检请求
,它虽然没有添加这个拦截器,但是它的PreFlightHandler的handler
仅仅调用DefaultCorsProcessor的processRequest方法
而没有处理器方法的调用,这样就同样避免了业务代码的执行。注意,如果没有配置CorsConfiguration(无论是全局的还是局部的)
,或者请求不是预检请求
,那么即使是CORS请求
到来时,业务代码会按照正常请求流程来执行,也就不会有CORS校验
,虽然浏览器不会
呈现最终结果!
每一个拦截器的preHandle
方法返回true
,则会使用interceptorIndex
变量记录该拦截器在拦截器链中的下标索引。
需要注意到,如果某个拦截器的preHandle方法返回false
,则执行triggerAfterCompletion
方法,triggerAfterCompletion方法将会对拦截器链中之前通过的(即preHandle方法返回true)
的拦截器倒序的执行afterCompletion
方法,最后返回false
。
//HandlerExecutionChain的属性
/**
* 当前应用的拦截器在拦截器链中的索引位置
*/
private int interceptorIndex = -1;
/**
* 通过构造器指定的拦截器数组,要应用的拦截器链数组
*/
@Nullable
private HandlerInterceptor[] interceptors;
/**
* initInterceptorList方法初始化的拦截器集合
*/
@Nullable
private List<HandlerInterceptor> interceptorList;
/**
* HandlerExecutionChain的方法
*
* 应用此Chain中注册的拦截器的preHandle方法。
*
* @return 如果执行链应该继续下一个拦截器或处理程序本身,则返回true。否则,DispatcherServlet假定此拦截器已经处理了响应本身,随后将直接返回。
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
//获取此前找到的拦截器链
HandlerInterceptor[] interceptors = getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for (int i = 0; i < interceptors.length; i++) {
HandlerInterceptor interceptor = interceptors[i];
//执行preHandle方法,如果某个拦截器的preHandle方法返回false则执行triggerAfterCompletion方法,随后返回false
if (!interceptor.preHandle(request, response, this.handler)) {
//执行triggerAfterCompletion方法
//即对拦截器链中之前通过的拦截器执行afterCompletion方法
triggerAfterCompletion(request, response, null);
return false;
}
//改变当前执行的拦截器索引
this.interceptorIndex = i;
}
}
//所有的拦截器都通过,那么返回true
return true;
}
如果此前的handler和HandlerAdapter都找到了,并且通过了所有拦截器的preHandle校验,那么通过HandlerAdapter
使用给定的handler
实际处理请求,返回一个ModelAndView
结果对象。不同的handler的实际工作流程差别非常大,其中还包括序列化、数据绑定、检验、返回值处理
等等步骤。
对于HandlerMethod
来说,它是通过RequestMappingHandlerAdapter
来调用的,最终目的就会尝试执行对应的@ReuqestMapping
方法,也就是业务逻辑
。
我们直接看RequestMappingHandlerAdapter的handle方法
的逻辑就行了,它的handle方法是由父类AbstractHandlerMethodAdapter
实现的!
/**
1. AbstractHandlerMethodAdapter的方法
2.
3. 使用给定的handler来处理此请求,不同的handler所需的工作流程可能相差很大。
4. 此实现期望handler为HandlerMethod类型。
5. 6. @return 具有视图名称和所需模型数据的ModelAndView对象;如果直接处理了该请求(比如application/json),则返回null
*/
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
//委托另一个handleInternal方法处理请求,该方法由子类RequestMappingHandlerAdapter实现
return handleInternal(request, response, (HandlerMethod) handler);
}
内部委托另一个handleInternal
方法处理请求,该方法由子类RequestMappingHandlerAdapter
实现。
checkRequest
方法,检查是否是支持的请求方法,检查是否必须session,如果不满足条件则抛出异常。invokeHandlerMethod
方法真正的处理请求,执行handlerMethod
方法,返回ModelAndView
。Cache-Control
响应头,那么帮助处理。/**
* RequestMappingHandlerAdapter的方法
*
* 使用给定的HandlerMethod处理请求,真正的处理请求的入口
*
* @param request 当前request
* @param response 当前response
* @param handlerMethod 当前handlerMethod
* @return 具有视图名称和所需模型数据的ModelAndView对象;如果直接处理了请求(比如application/json),则为null
*/
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
//检查是否是支持的请求方法,检查是否必须session,如果不满足条件则抛出异常
checkRequest(request);
//如果需要,在同步块中执行invokeHandlerMethod,默认为false
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 {
//完全不需要session同步,则调用直接调用invokeHandlerMethod,这是常见逻辑
mav = invokeHandlerMethod(request, response, handlerMethod);
}
//如果自己没有设置Cache-Control响应头,那么帮助处理,一般都没有
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
} else {
prepareResponse(response);
}
}
//返回ModelAndView
return mav;
}
在handleInternal
方法中,最终是通过执行invokeHandlerMethod
方法来执行HandlerMethod并处理请求的。该方法也是这个适配器中最重要的方法,它用于调用我们的目标方法,同时会进行数据装换、绑定、检验、设置返回值
等工作。
最终返回一个ModelAndView
对象,可用于视图解析,因此对于不需要视图解析的请求,比如application/json
,则返回的是null
。
/**
* RequestMappingHandlerAdapter的方法
*
* 执行HandlerMethod,尝试返回ModelAndView以用于视图解析
*/
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
//Request和Response包装为一个ServletWebRequest
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
/*
* 1 获取WebDataBinderFactory,用于为当前请求创建一个 WebDataBinder实例,用于参数绑定
*/
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
/*
* 2 获取ModelFactory,用于在控制器方法调用之前协助初始化Model,并在调用之后协助对其进行更新。
*/
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
/*
* 3 根据给定的HandlerMethod定义创建一个ServletInvocableHandlerMethod,具有执行HandlerMethod的能力。
* 还扩展了InvocableHandlerMethod的能力,使其能够通过注册的HandlerMethodReturnValueHandler处理返回值,
* 并且还支持基于方法级别的@ResponseStatus注解设置响应状态。
*/
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
/*
* 4 设置各种要用到的工具类属性
*/
//设置参数解析器
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
//设置返回值解析器
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
//设置DataBinderFactory
invocableMethod.setDataBinderFactory(binderFactory);
//设置ParameterNameDiscoverer
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
/*
* 4 创建ModelAndViewContainer并初始化Model对象的属性
*/
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//将上个请求传递的FlashMap的值设置到mavContainer
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
/*
* 初始化Model对象,并填充模型属性
* 1、从request中检索类上的@SessionAttributes的属性名对应的值并设置到mavContainer(只有没有的key才会存储)
* 2、调用@ModelAttribute 注解标注的方法,并尝试将返回结果设置到mavContainer(只有没有的key才会存储)
* 3、查找方法中标注了@ModelAttribute注解的方法参数并在@SessionAttributes中根据name 和type匹配查找属性名
* 如果找到了属性名,则从request中检索该属性名的属性值并设置到mavContainer(只有没有的key才会存储)
*/
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
/*
* 设置默认model中的数据在渲染和重定向中是否都可使用,默认可以,通过ignoreDefaultModelOnRedirect参数关闭
* 或者如果控制器方法中存在RedirectAttributes参数,那么将使用该参数来确定在重定向中使用的参数
*/
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
/*
* 5 异步请求处理相关,后面单独讲
*/
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
/*
* 6 核心方法,真正的执行handlerMethod方法并且进行处理,比如参数封装、校验、返回值处理成View对象或者JSON数据等等
* 执行的结果会被放在mavContainer中,可能只有Model或者只有VIew或者都没有(比如application/json请求)
*/
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
/*
* 7 根据mavContainer、modelFactory、webRequest内容获取ModelAndView对象
* ModelAndView对象中包含了Model、View、HttpStatus
*/
return getModelAndView(mavContainer, modelFactory, webRequest);
} finally {
//在finally块中,发出请求已完成的信号。
//将会执行所有请求销毁回调,并更新在请求处理期间已访问的会话属性,最后将requestActive标志改为false
webRequest.requestCompleted();
}
}
调用控制器方法,并通过已配置的HandlerMethodReturnValueHandler
其中之一处理返回值。它用于调用我们的目标方法,同时会进行数据装换、绑定、检验、返回值处理成View对象或者JSON数据
等等工作。
执行的结果会被放在mavContainer
中,可能只有Model
或者只有VIew
或者都没有(比如application/json请求)
。
/**
1. ServletInvocableHandlerMethod的方法
2.
3. 调用控制器方法并通过已配置的HandlerMethodReturnValueHandlers其中之一处理返回值。
4. 5. @param webRequest 当前 request
6. @param mavContainer 此请求的ModelAndViewContainer
7. @param providedArgs 按类型匹配的“给定”参数(未解析)
*/
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
/*
* 1 在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回方法执行返回的原始值。
* 这一步的执行逻辑是:参数封装、校验——@Validated校验(如果存在)——调用真实方法——返回原始执行结果
*/
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
/*
* 尝试设置响应状态码,这是为了支持方法上的@ResponseStatus注解
*/
setResponseStatus(webRequest);
//如果没有返回值
if (returnValue == null) {
//如果请求已被处理
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
//设置requestHandled属性为true,表示请求已被处理,方法结束
//实际上,如果具有void返回类型(或null返回值)的方法也具有ServletResponse、
//OutputStream参数或@ResponseStatus注解,那么requestHandled属性会被设置为true
mavContainer.setRequestHandled(true);
return;
}
//否则,如果存在responseStatusReason属性,即@ResponseStatus存在reason
} else if (StringUtils.hasText(getResponseStatusReason())) {
//设置requestHandled属性为true,表示请求已被处理,方法结束
mavContainer.setRequestHandled(true);
return;
}
//到这里,表示请求未被处理完成,后面还要继续处理。一般没有@ResponseStatus注解并且方法正常完成的请求都会走这里
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
/*
* 2 使用HandlerMethodReturnValueHandlerComposite中的HandlerMethodReturnValueHandler来处理返回值
*
* 通过向Model模型添加属性并设置View视图或调用ModelAndViewContainer.setRequestHandled
* 方法设置为true来处理给定的返回值,以指示已直接处理响应,后续不需要处理。
*/
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
} catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
首先在给定请求的上下文中解析方法参数
,后使用解析出来的参数值调用
该方法,返回方法执行返回
的原始值。这一步的执行逻辑是:参数封装、校验——@Validated校验(如果存在)——调用真实方法——返回原始执行结果
。
getMethodArgumentValues
方法获取方法参数
,并为每个参数选择适当的参数解析器,解析、获取
方法参数,最后按照参数顺序返回解析后的参数数组,Spring MVC的入参都是通过HandlerMethodArgumentResolver
体系来解析的,其提供了几十个该类型的入参解析器。该过程中会进行依赖项
的校验,比如某个参数有@RequestParam注解并且required=true
,但是如果该参数不存在,那么就会抛出异常
。该过程中还会执行@InitBinder方法
。
@RequestBody
注解标注的方法参数,将会使用RequestResponseBodyMethodProcessor
这个解析器来解析,实际上是依赖其内部的messageConverters
来执行解析的。对于application/json
请求,默认将会使用MappingJackson2HttpMessageConverter
进行JSON反序列化
。doInvoke
方法,根据解析出来的参数数组反射调用目标方法
,返回原始结果
,如果该方法还应用了@Validated校验
,那么此时又会进行Validated
校验规则,实际上此时的类对象是一个代理对象
,它的控制器方法已被AOP增强
,在方法实际调用之前将会应用Validated校验方法
。/**
* InvocableHandlerMethod的方法
*
* 在给定请求的上下文中解析方法参数,后使用解析出来的参数值调用该方法,返回原始结果
*
* 参数值通常通过HandlerMethodArgumentResolvers进行解析,但是,providedArgs参数可以提供要直接使用的参数值,即无需参数解析。
* 提供的参数值的示例包括WebDataBinder,SessionStatus或引发的异常实例,在参数解析之前检查提供的参数值。
*
* @param request 当前 request
* @param mavContainer 此请求的ModelAndViewContainer
* @param providedArgs 按类型匹配的“给定”参数,未解析,可以不传
* @return 调用的方法返回的原始值
*/
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
/*
* 1 获取方法参数,并为每个参数选择适当的参数解析器,解析、获取方法参数,最后按照参数顺序返回解析后的参数数组
*
* Spring MVC的入参都是通过HandlerMethodArgumentResolver体系来解析的,其提供了几十个该类型的入参解析器
* 该过程中会进行依赖项的校验,比如某个参数有@RequestParam注解并且required=true,但是如果该参数不存在,那么就会抛出异常
* 该过程中还会执行@InitBinder方法
*
* 对于常见的@RequestBody标注的方法参数,将会使用RequestResponseBodyMethodProcessor这个解析器来解析
* 实际上是依赖其内部的messageConverters来执行解析的。
* 对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON反序列化
*/
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
/*
* 2 根据解析出来的参数数组反射调用目标方法,返回原始结果
*
* 如果该方法还应用了@Validated校验,那么此时又会进行Validated校验规则,实际上此时的类对象是一个代理对象
* 它的控制器方法已被AOP增强,在方法实际调用之前将会应用Validated校验方法
*/
return doInvoke(args);
}
遍历已注册的HandlerMethodReturnValueHandler
并调用支持该返回值的那个处理器来处理返回值
。
对于常见的@ResponseBody
标注的方法,将会使用RequestResponseBodyMethodProcessor
这个解析器来解析,实际上是依赖其内部的messageConverters
来执行解析的,对于application/json
请求,默认将会使用MappingJackson2HttpMessageConverter
进行JSON序列化
,并将JSON字符串结果设置到response
中,随后会调用mavContainer.setRequestHandled
方法设置为true
来指示已直接处理响应
,后续无需解析、处理视图。
/**
* HandlerMethodReturnValueHandlerComposite的方法
*
* 遍历已注册的HandlerMethodReturnValueHandlers并调用支持该返回值和返回值的那个处理器来处理返回值
*/
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
/*
* 1 根据返回的原始返回值和类型选择可以处理的HandlerMethodReturnValueHandler
*
* 对于常见的@ResponseBody标注的方法,将会使用RequestResponseBodyMethodProcessor这个解析器来解析
* 实际上是依赖其内部的messageConverters来执行解析的
* 对于application/json请求,默认将会使用MappingJackson2HttpMessageConverter进行JSON序列化,
* 并将JSON字符串结果设置到response中
*/
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
//没找到就抛出异常
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
/*
* 2 通过HandlerMethodReturnValueHandler处理返回值
* 通过向Model模型添加属性并向mavContainer设置View视图
* 或调用mavContainer.setRequestHandled方法设置为true来指示已直接处理响应,通常application/json请求会执行该操作
*/
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
获取ModelAndView
,用以渲染视图,如果不需要渲染视图,则返回null
,此情况通常是已将返回的数据写入response
中了,比如application/json
请求。
这里的ModelAndView
中通常包含的是model和viewName
,model
实际上是一个ModelMap
,保存着一系列的数据,而在前后端不分离的项目中,controller方法所要跳转时返回的String字符串就是视图名
,比如"/xx/xx"、"redirect:/xx"、"forward:/xx"
。随后将会根据视图名
解析为View
视图对象。
/**
1. RequestMappingHandlerAdapter的方法
2.
3. 获取ModelAndView,用以渲染视图,如果不需要渲染视图
4. 则返回null,此情况通常是已将返回的数据写入response中了,比如application/json请求
*/
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
/*
* 1 更新Model数据
* 也就是将列为@SessionAttributes的Model属性提升到session会话级别,还可能添加BindingResult属性。
*/
modelFactory.updateModel(webRequest, mavContainer);
//如果此请求已被处理完毕,比如application/json请求返回的JSON数据已被写入到了response中
//那么不需要返回ModelAndView,也不需要渲染视图,返回null即可
if (mavContainer.isRequestHandled()) {
return null;
}
/*
* 2 设置ModelAndView,用于视图渲染
*/
//获取model
ModelMap model = mavContainer.getModel();
/*
* 创建一个ModelAndView对象,这里的view就是viewName,如果view是String类型,则算作视图名,这通常就是视图资源路径
* 在前后端不分离的项目中,controller方法返回的String字符串就是视图名,比如"/xx/xx"、"redirect:/xx"、"forward:/xx"
*/
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
//判断是否不是视图名,如果不是视图名称,则直接设置为视图对象
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
/*
* 3 重定向属性传递的处理
*/
//如果model属于RedirectAttributes类型,即重定向,使用"redirect:"前缀进行重定向的都不是
if (model instanceof RedirectAttributes) {
//获取flashAttributes,用于重定向时参数的传递
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
//
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
//存入OutputFlashMap中,这是为后续请求保存的属性
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
处理执行过程中的异常,或者根据返回的ModelAndView
结果渲染视图,最后倒序执行拦截器链中所有拦截器的afterCompletion
处理方法。
HandlerExceptionResolvers
确定处理错误的ModelAndView
。ModelAndView
结果渲染视图。首先可能会调用viewResolvers
中的全部ViewResolver
尝试将给定的视图名称解析为一个View
对象(待呈现),随后调用View
本身的render
方法,真正的执行渲染的工作。afterCompletion
处理方法。/**
1. DispatcherServlet的方法
2.
3. 处理执行过程中的异常,或者根据返回的ModelAndView结果渲染视图
4. 最后倒序执行拦截器链中所有拦截器的afterCompletion处理方法
5. 6. @param request 当前request
7. @param response 当前response
8. @param mappedHandler 当前mappedHandler
9. @param mv 执行返回的ModelAndView,可以为null
10. @param exception 此前处理过程中抛出的异常,可以为null
*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
//是否设置了异常视图
boolean errorView = false;
/*
* 1 如果此前抛出了异常,则处理异常
*/
if (exception != null) {
//如果属于ModelAndViewDefiningException异常,该异常表示此请求应转发给具有特定model的特定view
//这个异常通常是在业务代码中手动抛出的,用于将请求转发到错误视图
if (exception instanceof ModelAndViewDefiningException) {
logger.debug("ModelAndViewDefiningException encountered", exception);
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
//其他的异常
//获取handler
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
/*
* 通过注册的HandlerExceptionResolvers确定处理错误的ModelAndView
*
* 尝试执行配置的全部HandlerExceptionResolver的resolveException方法,返回一个ModelAndView的结果
* 如果某个异常解析器的返回值不为null,则基于该ModelAndView返回结果,否则将抛出参数中的异常
*/
mv = processHandlerException(request, response, handler, exception);
//如果确定了处理错误的ModelAndView,则errorView为true
errorView = (mv != null);
}
}
/*
* 2 渲染视图
*/
//判断是否需要渲染视图,即mv不为null并且内部的View不为null并且内部的Model也不为空
if (mv != null && !mv.wasCleared()) {
/*
* 渲染给定的ModelAndView,即渲染视图
* 这是处理请求的最后阶段,它可能涉及按视图名称来解析视图。
*/
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) {
/*
* 3 倒序应用拦截器链中的此前找到的所有拦截器的afterCompletion处理方法
*
* 某个afterCompletion方法在执行中即使抛出异常,也会被catch掉,不会影响其他方法的执行
*/
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
如果返回的ModelAndView不为null,并且内部的View不为null,并且内部的Model也不为空,那么就渲染给定的ModelAndView。这是处理请求的最后阶段,它可能涉及按视图名称解析为视图对象的逻辑。
视图名
,则调用viewResolvers
中的全部ViewResolver
尝试将给定的视图名称解析为一个View对象(待呈现)
。View的render
方法,真正的执行渲染
的工作,不同的View实现执行不同的渲染逻辑,常见的InternalResourceView用于渲染JSP视图
。/**
* DispatcherServlet的方法
*
* 渲染给定的ModelAndView。这是处理请求的最后阶段,它可能涉及按名称解析视图。
*
* @param mv 要渲染的ModelAndView
* @param request 当前的HTTP Servlet请求
* @param response 当前的HTTP Servlet响应
*/
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
//确定请求的语言环境并将其应用于响应,用于国际化
Locale locale =
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);
View view;
//获取取视图名,如果view是String类型,那么view就是viewName,否则返回null。
String viewName = mv.getViewName();
//大多数情况都是viewName都不为null
if (viewName != null) {
/*
* 1 调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个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() + "'");
}
}
/*
* 委托给View对象进行渲染,也就是说具体的渲染工作是由View对象本身来完成的
*/
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
//设置相应状态码
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
}
/*
* 2 调用View的render方法,真正的执行渲染的工作
*
* 渲染工作由View自己来完成,不同的View实现执行不同的渲染逻辑,常见的InternalResourceView用于渲染JSP视图
*/
view.render(mv.getModelInternal(), request, response);
} catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
遍历viewResolvers
,依次调用每一个ViewResolver的resolveViewName
方法,尝试将给定的视图名
称解析为一个View视图对象(待呈现)
。如果某个返回结果不为null
,则使用该返回的View
。
默认的viewResolver
只有一个InternalResourceViewResolver
,它将解析为viewName
为InternalResourceView(请求转发或者包含)或者RedirectView(请求重定向)
。这两个View类中的url
属性就是viewName
处理后的结果,也就是说viewName
实际上就包含资源路径
。
/**
* DispatcherServlet的属性
*
* 此servlet使用的ViewResolvers列表。
*/
@Nullable
private List<ViewResolver> viewResolvers;
/**
* DispatcherServlet的方法
*
* 调用viewResolvers中的全部ViewResolver尝试将给定的视图名称解析为一个View对象(待呈现)。
*/
@Nullable
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {
if (this.viewResolvers != null) {
/*
* 遍历viewResolvers,依次调用每一个ViewResolver的resolveViewName方法,尝试获取View
* 如果某个返回结果不为null,则使用该返回的View
*
* 默认的viewResolver只有一个InternalResourceViewResolver,它将解析为viewName为
* InternalResourceView或者RedirectView(具有重定向前缀"redirect:")
*/
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
return view;
}
}
}
//没有解析成功,返回null
return null;
}
真正的渲染视图是通过调用View自己的render方法
来实现的。
不同的View执行不同的渲染逻辑,常见的JSP等视图文件都是通过InternalResourceView/RedirectView
来渲染的,而如果引入JSTL
,则将使用JstlView
,如果是FreeMarker
视图则是通过FreeMarkerView
渲染的。
/**
* AbstractView的方法
*
* 渲染视图
*/
@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(绝不为null)。
Map<String, Object> mergedModel = createMergedOutputModel(model, request, response);
//准备响应,主要是对于视图生成下载内容的处理,比如PDF视图
prepareResponse(request, response);
/*
* 委托renderMergedOutputModel方法进行实际渲染,该方法由子类提供的具体实现
*/
renderMergedOutputModel(mergedModel, getRequestToExpose(request), response);
}
来看看子类InternalResourceView的renderMergedOutputModel
方法,它用于执行请求转发和请求包含,最终会委托给RequestDispatcher的forward 或者include
这两个原始方法。
/**
* InternalResourceView的方法
*
* 根据指定的模型渲染内部资源,这包括将模型设置为请求属性。
*/
@Override
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
//公开模型对象作为请求属性,即将model的属性设置到request中(通过request.setAttribute方法)
exposeModelAsRequestAttributes(model, request);
//公开每个渲染操作特有的帮助器,通常JSTL的渲染需要
exposeHelpers(request);
// 准备渲染,并确定要转发到(或包括请求包含)的请求分派器路径,实际上此实现仅返回配置的url属性,也就是viewName处理后的结果。
String dispatcherPath = prepareForRendering(request, response);
//获取目标资源(通常是JSP)的RequestDispatcher
//实际上就是request.getRequestDispatcher(path)方法
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!");
}
//如果是包含请求或响应已经提交,则执行请求包含,否则执行请求转发。
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
//最终会执行RequestDispatcher#include这个原始方法
rd.include(request, response);
} else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
//最终会执行RequestDispatcher#forward这个原始方法
rd.forward(request, response);
}
}
而对于RedirectView
来说,最终会调用response.sendRedirect
方法完成请求重定向。
上面我们详细介绍了Spring MVC处理请求的入口以及核心流程。
源码入口就是FrameworkServlet#service
方法,而核心的请求处理类就是DispatcherServlet
。
在学习了源码之后,我们可以总结出Spring MVC处理请求大概流程:
确定当前请求的处理器——handler
。通过遍历handlerMappings,依次调用每个HandlerMapping
的getHandler
方法获取HandlerExecutionChain
对象,只要有一个HandlerMapping的getHandler方法返回值不为null,就返回该返回值,就不会继续向后查找,如果在所有的handlerMapping中都没找到,则返回null。对于RequestMappingHandlerMapping
,他返回的handler就是HandlerMethod
。该过程中还会进行CORS的配置
,用于后续CORS请求的验证、处理。
HandlerExecutionChain
对象,它包含了真正的handler以及适用于当前handler的HandlerInterceptor拦截器链
。返回404响应码或者抛出NoHandlerFoundException异常
。根据handler确定当前请求的处理器适配器——HandlerAdapter
。通过遍历handlerAdapters,依次调用每个HandlerAdapter
的supports
方法,只要有一个HandlerAdapter的supports方法返回true,就返回该HandlerAdapter,就不会继续向后查找,如果在所有的HandlerAdapter中都没找到,则直接抛出ServletException
。对于HandlerMethod
,它的适配器就是RequestMappingHandlerAdapter
。顺序应用拦截器链中所有拦截器的PreHandle预处理方法
,只要有一个拦截器不通过,那么该请求就不会继续处理,而是对拦截器链中之前通过的(即此前的preHandle方法返回true)的拦截器倒序
的执行afterCompletion
方法,最后返回。如果都通过
,那么继续后续处理。通过HandlerAdapter来执行给定的handler,将返回一个ModelAndView结果对象(可能返回null)
。
HandlerMethod
来说,它是通过RequestMappingHandlerAdapter
来调用的,最终目的就会尝试执行对应的@ReuqestMapping方法
,也就是业务逻辑,其中还包括参数反序列化、数据绑定、检验、返回值处理、序列化
等等步骤。ModelAndView对象的Model部分是业务返回的模型数据,它是一个map,View部分为视图的逻辑视图名,即ViewName
。ModelAndView
用以渲染视图
,如果不需要
渲染视图,则返回null
,此情况通常是已将返回的数据序列化为JSON字符串写入response中了,比如application/json请求(通常是前后端分离的项目)
。handler正常处理完毕时,倒序应用拦截器链中的所有拦截器的postHandle后处理方法
。处理handler执行过程中抛出的异常(如果有)
,将会通过注册的HandlerExceptionResolvers
确定处理错误的ModelAndView
,借此我们可以对异常转发到统一配置的错误页面,或者直接返回错误的JSON数据
!根据ModelAndView(无论是正产返回的还是错误处理的)渲染视图,如果此前返回的ModelAndView为null,则这一步不会执行。
ViewResolver
尝试将给定的视图名
称解析为一个View对象(待呈现,内部保存了经过逻辑视图名viewName解析出来的物理视图资源URL路径)
。View本身的render方法
,真正的执行渲染
的工作。但是,如果更加严格
的话,View 更多的是用于在将模型数据移交给特定视图技术之前进行数据准备,而不是真正的进行视图渲染,视图渲染仍然依靠其该类型视图本身的视图渲染技术!
简单的转发请求或者包含请求
,如果目标是JSP视图
,则View就是InternalResourceView
,在渲染时首先就是将model
数据存储到request域属性
中,然后通过RequestDispatcher直接将请求include包含或者forward转发到物理视图路径URL对应的JSP资源
,而include/forward
方法则是我们在之前就见过的原始的Servlet API,最终还是通过response响应给用户的(对JSP来说就是将JSP渲染之后通过response输出为HTML文本)。
View中仅仅是进行了数据的封装和准备(比如将model数据存储到request域属性中),并没有进行实际渲染视图的工作(仅仅是调用include/forward方法),对于JSP视图来说,真正的视图渲染和数据填充以及返回响应仍然是依赖最原始JSP技术,与Spring MVC无关,与View无关。
无论请求处理成功还是失败抛出异常
,都会倒序
应用拦截器链
中的所有拦截器的afterCompletion
处理方法,表示请求处理完毕
。 为此,我们给出一幅比较完整的Spring MVC请求执行流程图:
相关文章:
https://spring.io/
Spring Framework 5.x 学习
Spring MVC 5.x 学习
Spring Framework 5.x 源码
如有需要交流,或者文章有误,请直接留言。另外希望点赞、收藏、关注,我将不间断更新各种Java学习博客!