本文内容假设阅读者对Spring MVC 源码阅读-框架启动过程中包含的知识点已经有所了解。
本文的配置也是基于xml形式进行的。
文章中的一些称呼的说明
1.在Controller中创建的@RequestMapping
在下文中称为路径映射
2.在Controller中创建的路径映射方法在下文中称为请求处理方法
@GetMapping("/a") // 路径映射
public String a() { // 请求处理方法
return "";
}
关于框架启动过程的文章中,已经指出Spring MVC是基于
DispatcherServlet
展开的,而DispatcherServlet
又继承自GenericServlet
,
也就是说Servlet
的生命周期代表了Spring MVC
的生命周期。
其中init(ServletConfig)
代表了Spring MVC的启动,那么service(ServletRequest, ServletResponse)
也就应该代表Spring MVC的请求的处理流程。
1. Spring MVC 请求处理流程
从DispatcherServlet
向上寻找service(ServletRequest, ServletResponse)
,最终会在HttpServlet
中找到该方法。
// HttpServlet
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
if (!(req instanceof HttpServletRequest && res instanceof HttpServletResponse)) {
throw new ServletException("non-HTTP request or response");
}
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
// 这里的service方法也需要从DispatcherServlet开始自下向上查找,
// 可以发现最早出现该方法的是FrameworkServlet,那这个方法调用的就是FrameworkServlet里面的
service(request, response);
}
简单的对
ServletRequest
和ServletResponse
进行了类型转换,随后调用service(HttpServletRequest, HttpServletResponse)
方法
1.1. FrameworkServlet#service(HttpServletRequest, HttpServletResponse)
当请求刚到来时,第一步就是确认请求方式,以便使用特定的方式对请求进行业务处理。
HttpServlet#service(HttpServletRequest, HttpServletResponse)
中提供了针对GET
、HEADER
、POST
、PUT
、DELETE
、OPTIONS
、TRACE
7种请求方式的处理。
FrameworkServlet#service(HttpServletRequest, HttpServletResponse)
中提供了PATCH
请求方式的处理。
由于方法太多,在下文中将围绕POST
请求方式展开逻辑描述。
// FrameworkServlet
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
else {
super.service(request, response);
}
}
// HttpServlet
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
// ...
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
// 这里的doPost方法也需要从DispatcherServlet开始自下向上查找,FrameworkServlet类中最早出现该方法
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
// FrameworkServlet
protected final void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 不管是doPost、doGet还是doDelete等,它们都要调用了这个方法
processRequest(request, response);
}
1.2 FrameworkServlet#processRequest(HttpServletRequest, HttpServletResponse)
processRequest
方法中做了这么几件事情
1.将HttpServletRequest
和HttpServletResponse
对象 以及 从HttpServletRequest
中解析出来的Locale
存放到线程本地内。
2.根据当前的请求创建一个异步管理程序来支持对异步返回值的处理。
3.调用请求处理的方法(doService(request, response);
)。
4.进行收尾工作
~ 清除本次请求产生的数据
~ 调用注册的销毁逻辑
~ 对Session
的内容进行一些操作
~ 向容器内发送一个ServletRequestHandledEvent
类型的事件
// FrameworkServlet
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
long startTime = System.currentTimeMillis();
Throwable failureCause = null;
// ---------------------------------------------------- 获取 Locale ---------------------------------------------------
// 从 ThreadLocal 中获取与当前线程相关联的 LocaleContext,LocaleContext 里面存放的是一个 Locale 对象。
LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
// buildLocaleContext方法,也要从 DispatcherServlet 类向上找,最早在 DispatcherServlet 中找到该方法。
// 默认情况下,会使用到在Spring MVC框架启动过程中初始化的 LocaleResolver 来解析请求中的 Locale 对象
LocaleContext localeContext = buildLocaleContext(request);
// ---------------------------------------------- 获取 RequestAttributes -----------------------------------------------
// 从 ThreadLocal 中获取与当前线程相关联的 RequestAttributes,RequestAttributes 内存储的是 request、response等内容
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
// 通过创建 ServletRequestAttributes对象实例 来 存储Request和Response。
// 除此之外,ServletRequestAttributes 还提供了 对Request和Session属性的查找与修改,使用见之后的分析。
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);
// ------------------------------------------ Spring MVC 对异步请求的支持 ------------------------------------------
// 创建 WebAsyncManager 对象,并放到 request 中
// 如果当前请求是因为 AsyncContext#dispatch() 方法的调用而产生,那么这个请求内部已经被Servlet容器设置了这个值。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
// 使用指定的key注册一个拦截器,在 异步返回值(例如:Callable) 被执行前后会被调用。
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());
// ------------------------------------------- 对 ThreadLocal 进行设置 -------------------------------------------
// 将 localeContext 和 requestAttributes 存放到 ThreadLocal 中
// (其中又具体区分 ThreadLocal 是否是可继承的,上面的带有previous的LocaleContext与RequestAttributes的获取都有涉及,这里不做说明)
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) {
/* 注意,这是 ServletRequestAttributes 内的收尾工作
* 1. 调用注册的跟request有关的(也就是scope为0)销毁方法 (虽然注册时使用的是Runnable类型,但执行时是直接调用run方法的,不是多线程执行的)
* 2. 对Session属性进行一些更新
* 3. 标记requestActive为false
*/
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
// 向 webApplicationContext 容器内发送一个 ServletRequestHandledEvent 类型的事件
publishRequestHandledEvent(request, response, startTime, failureCause);
}
}
// DispatcherServlet
protected LocaleContext buildLocaleContext(final HttpServletRequest request) {
// 这里获取的就是Spring MVC启动过程中,initLocaleResolver 策略中加载的默认本地化解析器(AcceptHeaderLocaleResolver)
LocaleResolver lr = this.localeResolver;
if (lr instanceof LocaleContextResolver) {
// 如果是CookieLocaleResolver、SessionLocaleResolver、FixedLocaleResolver才会到这里
return ((LocaleContextResolver) lr).resolveLocaleContext(request);
}
else {
// 默认的 AcceptHeaderLocaleResolver 会进入这里。
// 这里的返回值是一个lambda函数,其实是一个匿名内部类的写法。
return () -> (lr != null ? lr.resolveLocale(request) : request.getLocale());
}
}
// FrameworkServlet
protected ServletRequestAttributes buildRequestAttributes(HttpServletRequest request,
@Nullable HttpServletResponse response, @Nullable RequestAttributes previousAttributes) {
if (previousAttributes == null || previousAttributes instanceof ServletRequestAttributes) {
return new ServletRequestAttributes(request, response);
}
else {
return null; // preserve the pre-bound RequestAttributes instance
}
}
// FrameworkServlet
private void initContextHolders(HttpServletRequest request,
@Nullable LocaleContext localeContext, @Nullable RequestAttributes requestAttributes) {
if (localeContext != null) {
LocaleContextHolder.setLocaleContext(localeContext, this.threadContextInheritable);
}
if (requestAttributes != null) {
RequestContextHolder.setRequestAttributes(requestAttributes, this.threadContextInheritable);
}
}
// WebAsyncUtils
public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
WebAsyncManager asyncManager = null;
Object asyncManagerAttr = servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
if (asyncManagerAttr instanceof WebAsyncManager) {
asyncManager = (WebAsyncManager) asyncManagerAttr;
}
if (asyncManager == null) {
asyncManager = new WebAsyncManager();
servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
}
return asyncManager;
}
1.2.1 ServletRequestAttributes 的使用示例
public void test() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
// 获取 request 和 response
HttpServletRequest request = requestAttributes.getRequest();
HttpServletResponse response = requestAttributes.getResponse();
HttpServletRequest request1 = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 如果scope为0,那么操作的就是此次请求的属性,也就是request的请求;如果scope为1,那么操作的就是session的属性。
Object requestAttr = requestAttributes.getAttribute("属性名", 0);
Object sessionAttr = requestAttributes.getAttribute("属性名", 1);
requestAttributes.removeAttribute("属性名", 0);
requestAttributes.removeAttribute("属性名", 1);
// 注册一些自定义的逻辑,在请求处理完后会在扫尾工作时调用这些逻辑,虽然这里是Runnable类型,但并不是多线程执行的,而是直接调用的run方法。
requestAttributes.registerDestructionCallback("1", () -> {}, 0);
// 获取session的一些内容
String sessionId = requestAttributes.getSessionId();
Object sessionMutex = requestAttributes.getSessionMutex();
HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);
}
1.3 DispatcherServlet#doService(HttpServletRequest, HttpServletResponse)
doService
方法中中大概做了这么几件事情
1.对由include
发起的请求做特殊处理。
2.将框架内的属性交给HttpServletRequest
,以方便之后的处理流程使用。
~ Spring MVC 的 IOC容器
~ Local 的解析器
~ Theme 的解析器
~ FlashMap 相关内容
3.调用请求处理的方法(doDispatch(request, response);
)。
// DispatcherServlet
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
logRequest(request);
// --------------------------------------- 对RequestDispatcher#include造成的请求的处理 --------------------------------------------
// Keep a snapshot of the request attributes in case of an include,
// to be able to restore the original attributes after the include.
Map attributesSnapshot = null;
//
/*
* 检查请求中是否有 javax.servlet.include.request_uri 属性,如果有,就保留请求属性的快照,以便能够在include之后恢复原始属性。
*
* 这个东西和 RequestDispatcher#include(ServletRequest, ServletResponse) 有关,具体内容不做说明。
* 发挥作用的地方例如:
*/
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));
}
}
}
// ------------------------------------------ 将框架内的一些对象塞到request中 --------------------------------------------
// 将 Spring MVC框架内的一些对象 放到request内部,以使之后的 各种处理器 和 View 使用。
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext()); // Spring MVC的IOC容器
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// ----------------------------------------- FlashMapManager的处理 -------------------------------------------------------
// 这里就是 FlashMapManager 策略起作用的地方,它的使用在之前 Spring MVC 框架的启动流程中讲过
if (this.flashMapManager != null) {
FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
if (inputFlashMap != null) {
// 将FlashMap放入InputFlashMap中
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
}
// 因为这是请求的入口,所以OutputFlashMap内不应该存在任何值
request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
}
// ------------------------------------------ 处理请求 -------------------------------------------------------
try {
doDispatch(request, response);
}
finally {
// 判断当前请求已经调用过 AsyncWebRequest#startAsync(),不理解可以看完下面对异步请求的说明后再回头看这里。
// 如果当前请求没有变更为异步模式的话,这个条件才会成立,如果请求处理方法返回的是异步返回值,那条件就不成立。
if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
// 在 include 调度完成后,恢复上面快照内存储的请求原始属性
if (attributesSnapshot != null) {
restoreAttributesAfterInclude(request, attributesSnapshot);
}
}
}
}
1.4 DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse)
// DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
// HandlerExecutionChain 实际上是 HandlerMethod 和 相应的拦截器 的组合体
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// -------------------------------------------- 对 multipart/ 类型请求的识别(例如文件上传) ------------------------------------------
// 检查请求是否是跟multipart/类型的请求相关,如果符合条件就把request转为MultipartHttpServletRequest实现。
// 条件是: 1.有相应Multipart Request解析器 并且 2.是Multipart Request
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// -------------------------------------------- 寻找匹配的请求处理方法 ---------------------------------------------
// 从 解析路径映射的处理策略 中 查找到要执行的请求处理方法。 由于写的示例项目是基于注解扫描的,所以这里会使用 RequestMappingHandlerMapping 实现来查找。
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response); // 默认情况下,没找到匹配的路径映射就响应客户端404
return;
}
// ---------------------------------------- 寻找合适的 HandlerMethod 执行适配器 -------------------------------------
// 判断Hanlder/Controller是如何实现的,来使用特定的适配器执行我们的`请求处理方法`,在示例项目种默认情况下会获取到 RequestMappingHandlerAdapter 。
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ------------------------------------------ GET/HDEA请求的缓存机制 ------------------------------------------
// https://blog.csdn.net/snail_cjc/article/details/50778855
// 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;
}
}
// ------------------------------------------- 执行拦截器的PreHandle方法 --------------------------------------------
// 运行拦截器的preHandle方法,如果有拦截器的preHandle返回了false,那么此次请求直接停止,同时调用拦截器的afterCompletion方法。
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// ---------------------------------------------- 交由HandlerAdapter处理请求 --------------------------------------------------------
// 调用`请求处理方法`执行逻辑的地方
mv = ha.handler(processedRequest, response, mappedHandler.getHandler());
// 判断当前请求已经调用过 AsyncWebRequest#startAsync(),不理解可以看完下面对异步请求的说明后再回头看这里。
// 如果调用过,代表当前请求变更为异步模式,也就是说如果请求处理方法返回的是异步返回值,那条件就成立。
if (asyncManager.isConcurrentHandlingStarted()) {
return; // 如果请求已经转为异步模式,这里直接返回null,不需要执行下面的代码。
}
// ------------------------------------------- RequestToViewNameTranslator 逻辑的应用 ------------------------------------
// 如果`请求处理方法`执行完成后,仍然没有ViewName被设置(例如:请求处理方法 在 @Controller注解的类中 并且 方法返回值类型为void),
// 那么就使用 Spring MVC 框架启动过程中初始化的 RequestToViewNameTranslator 策略将请求地址解析为ViewName,
// 如果解析到的ViewName不为空,就填充到传入的 mv 中。
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);
}
// --------------------------------- 处理最终的视图 -------------------------------------
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) {
// 调用 AsyncHandlerInterceptor 类型拦截器的 applyAfterConcurrentHandlingStarted 方法
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// 清理 MultipartRequest 所使用的资源(文件上传方向的内容)。
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
doDispatch
都做了什么?
1.检查请求是否跟文件上传相关,以及确认相关后的一些转换。(与MultipartResolver
策略相关)
2.通过请求找到要执行的请求处理方法
。(与HandlerMapping
策略相关)
3.通过判断请求处理方法
所处的类是使用哪种方式成为Handler
来找到合适的HandlerAdapter
。(与HandlerAdapter
策略相关)
4.调用拦截器的preHandle
方法,如果被拦下来了,就执行拦截器的afterCompletion
方法。
5.通过获取到的HandlerAdapter
来正式执行请求处理方法
。
6.判断是否需要将请求路径解析成ViewName。(与RequestToViewNameTranslator
策略相关)
7.调用拦截器的postHandle
方法。
8.判断是否需要渲染视图。(与ViewResolvers
策略相关)
9.最后调用拦截器的afterCompletion
方法。(这一步和第8步是在一个方法内编写的,但两件事没关系)
10.清理multipart request
所产生的资源占用。
假设我们使用`@RestController`注解来使类成为`Handler`,
那么实际上在`第5步`,`请求处理方法`的返回值就已经写入了Response发送给了客户端,
在这种情况下,第6、8步骤实际上就不会发生,因为这两部与模板引擎技术相关,而在假设中,我们没有使用这一技术;
如果我们`@Controller`注解来使类称为`Handler`,同时没有使用`@ResponseBody`,那么第6、8步骤将会作用于`请求处理方法`的返回值。
同时,在这种假设下,我们的返回值可以为:没有返回值、null、ViewName、View、ModelAndeView
补充:
1.ViewName 实际上指的就是一个字符串而已,不过这个字符串会被认为指向了一个页面模板或者一个重定向地址等,最终会由不同的 ViewResolvers 解析为具体的 View 实现。
2.View 是 ViewName 的终点,每种模板引擎技术,都会为我们提供一个可用的 View 实现,这里可以简单理解为它就是个页面。
3.如果 ModelAndeView 调用无参构造函数创建,那实际上和返回一个null区别不大。
上面的一串步骤看起来还是很复杂,这里再简化一下,方便记忆。
1.对Http请求
进行处理。
2.找到请求处理方法
。
3.找到合适的HandlerAdapter
。
4.执行拦截器的先处理方法。
5.交由HandlerAdapter
执行请求处理方法
。
6.调用拦截器的后处理方法。
7.渲染页面模板
8.清理与收尾。
在下文会具体深入说明这些步骤的实现逻辑
1.4.1 寻找匹配的请求处理方法
// DispatcherServlet
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 默认情况下 handlerMappings 里存储的是 Spring MVC 框架启动时初始化的两个用于解析路径映射的处理策略
// BeanNameUrlHandlerMapping 和 RequestMappingHandlerMapping
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
// AbstractHandlerMapping
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
/* 这里要结合类结构图来看
* 如果当前 HandlerMapping 是 BeanNameUrlHandlerMapping,则会前往 AbstractUrlHandlerMapping#getHandlerInternal
* 如果当前 HandlerMapping 是 RequestMappingHandlerMapping,则会前往 AbstractHandlerMethodMapping#getHandlerInternal
*
* 最终返回的是要执行的请求处理方法。
*/
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
// 如果处理后handler为字符串, 根据该字符串拿bean
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 为给定的 请求处理方法 构建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());
}
if (CorsUtils.isCorsRequest(request)) {
// 配置Cors (跨域资源共享)
CorsConfiguration globalConfig = this.corsConfigurationSource.getCorsConfiguration(request);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
// AbstractHandlerMethodMapping
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
// 获取到要寻找的路径映射
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
this.mappingRegistry.acquireReadLock();
try {
// 从 AbstractHandlerMethodMapping.MappingRegistry的实例对象内 找到对应的 请求处理方法。
// AbstractHandlerMethodMapping.MappingRegistry 对象的构建在之前的文章内有提到,这里就不重复了
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
// AbstractHandlerMapping
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
// 转换 handler 的类型
HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ?
(HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
// 获取到要寻找的路径映射
String lookupPath = this.urlPathHelper.getLookupPathForRequest(request);
// 找到和请求路径匹配的拦截器,关于拦截器的添加方式在下文进行说明
for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
if (interceptor instanceof MappedInterceptor) {
MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
if (mappedInterceptor.matches(lookupPath, this.pathMatcher)) {
chain.addInterceptor(mappedInterceptor.getInterceptor());
}
}
else {
chain.addInterceptor(interceptor);
}
}
return chain;
}
HandlerExecutionChain
实际上就是HandlerMethod
和Spring MVC拦截器
的组合体。
1.4.1.1 Spring MVC 拦截器的配置
Spring MVC 默认情况下有2种添加方式。
// 第一种添加方式
// 第二种添加方式(手动配置HandlerMapping)
// 配置起作用的原理如下
// AbstractHandlerMapping
protected void initApplicationContext() throws BeansException {
// 一个空的实现
extendInterceptors(this.interceptors);
// 对应第一种配置方式
detectMappedInterceptors(this.adaptedInterceptors);
// 对应第二种配置方式
initInterceptors();
}
protected void detectMappedInterceptors(List mappedInterceptors) {
mappedInterceptors.addAll(
BeanFactoryUtils.beansOfTypeIncludingAncestors(
obtainApplicationContext(), MappedInterceptor.class, true, false).values());
}
protected void initInterceptors() {
if (!this.interceptors.isEmpty()) {
for (int i = 0; i < this.interceptors.size(); i++) {
Object interceptor = this.interceptors.get(i);
if (interceptor == null) {
throw new IllegalArgumentException("Entry number " + i + " in interceptors array is null");
}
this.adaptedInterceptors.add(adaptInterceptor(interceptor));
}
}
}
1.4.2 寻找合适的HandlerMethod执行适配器
因为使用的是
@Controller/@RestController
创建的Handler
,所以最后得到的是RequestMappingHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
// 默认情况下 handlerAdapters 里存储的是 Spring MVC 框架启动时初始化的三个功能扩展适配器,每个都对应了一类Handler的注册方式。
// 在上一篇文章中展示了有4种创建Handler的方式,这里不再重复。
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
// 因为我们使用的是 @Controller/@RestController 创建的Handler,所以返回的是 RequestMappingHandlerAdapter
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");
}
1.4.3 交由HandlerAdapter处理请求
在之前我们就确认了,在示例代码的默认情况下最终会由
RequestMappingHandlerAdapter
来执行,所以直接看它的代码。
// AbstractHandlerMethodAdapter
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
// RequestMappingHandlerAdapter
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// 对 请求方式 和 Session 的一些检查
checkRequest(request);
// ------------------------ 会话级上进行代码同步执行 ---------------------------------
// 在会话级别上进行代码同步执行,使得来自同一会话的请求串行访问该控制器,没遇到过合适的使用场景。
// 默认情况下,这个值是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 {
// ------------------------- 默认情况下,执行请求处理方法 ------------------------
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
// RequestMappingHandlerAdapter
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
/*
* 创建一个持有request和response的ServletWebRequest对象。
*
* 这个类的特殊之处在于它实现了NativeWebRequest接口,能够调用很多HttpServletRequest没有实现的方法,
* 在操作Last-Modified时或许会用到这个对象。
*/
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// ------------------------------ @InitBinder注解 的方法的相关的处理 -----------------------------
// 获取到所有有关的 @InitBinder 注解的方法,并把他们包装为一个 WebDataBinderFactory 工厂对象(新建)
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
// --------------------------- @SessionAttributes和@ModelAttribute注解 的方法的相关的处理 -----------------------------
/*
* 获取到 sessionAttributeStore 参数,并包装为一个 SessionAttributesHandler 对象(新建) 作为 param3,
* sessionAttributeStore 的值可以在 Spring MVC 的xml文件中进行配置,默认实现是DefaultSessionAttributeStore。
*
* 获取到所有有关的 @ModelAttribute 注解的方法 作为 param1。
*
* 将构建的param1、param3和传入的binderFactor(param2),这3个参数包装为一个 ModelFactory 工厂对象(新建)
*/
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
// ------------------------------- 对ServletInvocableHandlerMethod的进一步组装 ----------------------------------
// 使用 ServletInvocableHandlerMethod 对象实例包装 handlerMethod
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 设置参数解析器,argumentResolvers 的赋值过程在 Spring MVC 框架启动时进行
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
// 设置返回值解析器,returnValueHandlers 的赋值过程在 Spring MVC 框架启动时进行
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
// 设置跟 @InitBinder 注解有关的工厂对象
invocableMethod.setDataBinderFactory(binderFactory);
// 作用就跟名字一样---参数名词发现者,用于确认方法、构造函数的参数名称等作用,这个参数可以在Spring MVC的xml文件中配置,默认实现是 DefaultParameterNameDiscoverer
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// ------------------------------ 对ModelAndViewContainer对象的进一步组装 ---------------------------------------------
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
// 取出前文存放到request中的flashMap,然后存放到mavContainer里持有的的Model中
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 把一些相关的数据全部放到 Model 里面 (和 @SessionAttributes 和 @ModelAttribute 两个注解相关的内容的处理)
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
// 设置是否在重定向时忽略 Model, 不然Model内的数据会被拼接到url上, true 就是忽略。
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// ------------------------------ 对异步请求相关的处理 -----------------------------
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// 如果当前请求是因为 AsyncContext#dispatch() 方法的调用而产生,那么这个请求内部已经被Servlet容器设置了这个值,这里获取的也就是之前请求创建的那个实例。
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
// Callable的拦截器
asyncManager.registerCallableInterceptors(this.callableInterceptors);
// DeferredResult的拦截器
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// 如果上一个请求内已经设置了并发处理结果,那么就不需要再执行原来的请求处理方法了,直接处理这里得到的并发处理结果就行。
if (asyncManager.hasConcurrentResult()) { // 能进入这个判断的请求,必定是由 AsyncContext#dispatch() 方法的调用而产生请求。
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);
}
// ------------------------------ 执行请求处理方法并处理返回值 ------------------------------------
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// 判断当前请求是否已经转为异步模式,如果是,则条件成立,不理解可以看完下面对异步请求的说明后再回头看这里。
if (asyncManager.isConcurrentHandlingStarted()) { // 这个判断是作用域非 dispatch 导致的请求
return null;
}
// ------------------------------ 收尾工作 ----------------------------------
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
/* 做了如下三件事情:
* 调用我们自己注册的所有的 DestructionCallback 方法。 (在上面ServletRequestAttributes的使用示例中有注册示例)
* 将我们通过特定方式访问的Session属性进行回写。
* 把 ServletWebRequest 中的 requestActive 标记为 false。
*/
webRequest.requestCompleted();
}
}
1.4.3.1 @InitBinder注解的方法的处理
public static final MethodFilter INIT_BINDER_METHODS = method ->
AnnotatedElementUtils.hasAnnotation(method, InitBinder.class);
// RequestMappingHandlerAdapter
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
// 通过 HandlerMethod 获取到 Hanlder 类型
Class> handlerType = handlerMethod.getBeanType();
/*
* 从下面两段代码可以看出来 @InitBinder 有多种用法,起码这里就有2种了
* 1.在 Hanlder 类中直接定义
* 2.跟 @ControllerAdvice 注解配合使用
*/
// -------------------------------- 在Handler中跟 @InitBinder 有关的代码 -----------------------------------
// 从当前HandlerMethod所处的类中找到定义的所有@InitBinder注解的方法
// 首先从缓存中获取
Set methods = this.initBinderCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
// 把通过反射找到的类中所有 @InitBinder 方法都放到缓存中,来避免每次都通过反射获取
this.initBinderCache.put(handlerType, methods);
}
// ------------------------ 跟 @ControllerAdvice 和 @InitBinder 有关的代码 ------------------------
// 从所有被 @ControllerAdvice注解的类中,找到作用范围包含当前 HandlerMethod所处的类 的第一个,
// 然后将其中定义的所有@InitBinder注解的方法取出来
List initBinderMethods = new ArrayList<>();
// Global methods first
// initBinderAdviceCache 的赋值发生在Spring MVC 启动过程中(对 HandlerAdapter 策略初始化的时候)
// key与@ControllerAdvice注解的类有关 value是其中定义的被@InitBinder注解的所有方法
this.initBinderAdviceCache.forEach((clazz, methodSet) -> {
// 和@ControllerAdvice注解的作用范围有关,这个注解也是可以配置属性的,例如:basePackages、assignableTypes等
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for (Method method : methodSet) {
initBinderMethods.add(createInitBinderMethod(bean, method)); // 太过细节,不做过多关注
}
}
});
// -------------------- 将两种方式找到的 @InitBinder 合并到一个集合中 --------------------------------
// 将 methods 集合转换类型并存放到 initBinderMethods 中,initBinderMethods 是个List集合
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
// 使用 ServletRequestDataBinderFactory 对象包装 这些 @InitBinder方法,以方便后续使用
return createDataBinderFactory(initBinderMethods);
}
// RequestMappingHandlerAdapter
protected InitBinderDataBinderFactory createDataBinderFactory(List binderMethods)
throws Exception {
return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}
// RequestMappingHandlerAdapter
public WebBindingInitializer getWebBindingInitializer() {
return this.webBindingInitializer;
}
从上面的代码中可以直观的看出来
@InitBinder
的2种使用方式,实际上有3种,
第三种就是getWebBindingInitializer()
获取的webBindingInitializer
,这个东西我们可以通过配置来设置。
下面进行展示3种使用方法。
// com.demo.i.controller.MyController
// 第一种用法(在Handler中直接使用,这种方式只针对当前Handler生效)
@RestController
public class MyController {
@InitBinder
public void initBinder(WebDataBinder binder) {
// do something...
}
}
// 第二种用法(配合@ControllerAdvice进行全局配置,这种方式只针对扫描到的Handler生效)
// 如果@ControllerAdvice不指定范围,则代表针对所有Handler生效
@ControllerAdvice // (basePackages = "com.demo.i.controller")
public class MyControllerAdvice {
@InitBinder
public void initBinder(WebDataBinder binder) {
// do something...
}
}
// 第三种用法(全局配置)
@Bean
public RequestMappingHandlerAdapter webBindingInitializer() {
RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter();
adapter.setWebBindingInitializer(new WebBindingInitializer() {
// WebBindingInitializer中有两个入参的方法已经被启用
@Override
public void initBinder(WebDataBinder binder) {
// do something...
}
});
return adapter;
}
1.4.3.2 @SessionAttributes和@ModelAttribute注解的方法的处理
private SessionAttributeStore sessionAttributeStore = new DefaultSessionAttributeStore();
// 注意,这里是没有@RequestMapping注解,但有@ModelAttribute注解,不要少看了那个!
public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
(!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) &&
AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
// RequestMappingHandlerAdapter
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
// -------------------------------- 找到关联的SessionAttributesHandler ----------------------------
// -------------------------------- 这个东西是处理 @SessionAttributes 注解用的 -----------------------
// 找到和当前 handlerMethod 有关系的 SessionAttributesHandler
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
// 通过 HandlerMethod 获取到 Hanlder 类型
Class> handlerType = handlerMethod.getBeanType();
// -------------------------------- 找到Handler中被 @ModelAttribute 注释的方法 -------------------------------
Set methods = this.modelAttributeCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
// ------------------------ 跟 @ControllerAdvice 和 @ModelAttribute 有关的代码 ------------------------
// 从所有被 @ControllerAdvice注解的类中,找到作用范围包含当前 HandlerMethod所处的类 的第一个,
// 然后将其中定义的所有被@ModelAttribute注解的方法取出来
List attrMethods = new ArrayList<>();
// Global methods first
// modelAttributeAdviceCache 的赋值发生在Spring MVC 启动过程中(对 HandlerAdapter 策略初始化的时候)
// key与@ControllerAdvice注解的类有关 value是其中定义的被@InitBinder注解的所有方法
this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
if (clazz.isApplicableToBeanType(handlerType)) {
Object bean = clazz.resolveBean();
for (Method method : methodSet) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
});
// -------------------- 将两种方式找到的方法合并到一个集合中 --------------------------------
// 将 methods 集合转换类型并存放到 attrMethods 中,attrMethods 是个List集合
for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
// RequestMappingHandlerAdapter
private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod) {
// 通过 HandlerMethod 获取到 Hanlder 类型
Class> handlerType = handlerMethod.getBeanType();
// sessionAttributesHandlerCache 使用的Map是 ConcurrentHashMap 实现
SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
// DCL模式(Double Check Lock)
if (sessionAttrHandler == null) {
synchronized (this.sessionAttributesHandlerCache) {
sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
if (sessionAttrHandler == null) {
// 创建一个跟这个Handler有关系的SessionAttributesHandler对象实例。
// sessionAttributeStore 默认使用的是 DefaultSessionAttributeStore 实现。
sessionAttrHandler = new SessionAttributesHandler(handlerType, this.sessionAttributeStore);
this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
}
}
}
return sessionAttrHandler;
}
public SessionAttributesHandler(Class> handlerType, SessionAttributeStore sessionAttributeStore) {
Assert.notNull(sessionAttributeStore, "SessionAttributeStore may not be null");
this.sessionAttributeStore = sessionAttributeStore;
// 这里是对 @SessionAttributes 注解的处理,尝试从Handler上面找到 @SessionAttributes注解
SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType, SessionAttributes.class);
if (ann != null) {
Collections.addAll(this.attributeNames, ann.names());
Collections.addAll(this.attributeTypes, ann.types());
}
this.knownAttributeNames.addAll(this.attributeNames);
}
1.4.3.2.1 @ModelAttribute
从上面的代码中可以直观的看出来
@ModelAttribute
的2种总体使用方式,下面进行展示2种总体使用方法。
关于
Model
、ModelMap
、ModelAndView
,这是内容都是要配合ViewResolver
使用的。
也就是说,这些内容都是和模板引擎有关的,可以理解为就是负责在控制器和展现数据的视图之间传递数据用的。
其实这里的Model
体现的就是MVC模式中的M
,在下文中出现的Model
、ModelMap
实际指的是一个东西。
// 第一种用法(在Handler中直接使用,这种方式只针对当前Handler生效)
@Controller
public class MyController {
// 提供了更加详细的用法
// 如果访问 /test,这个方法会比test更早执行
// 抢先接收到参数a,然后进行处理,最后以a作为key添加到Model中,在test中尝试从Model中获取属性a时就可以拿到了
@ModelAttribute
public void myModelAttribute(@RequestParam String a, Model model) {
Map map = model.asMap();
map.put("a", a + " -> myModelAttribute -> test");
}
// 如果访问 /test,这个方法会比test更早执行
// 抢先接收到参数a,然后进行处理,最后以b作为key返回给Model,在test中尝试从Model中获取属性b时就可以拿到了
@ModelAttribute("b") // @ModelAttribute的value值将作为key,方法的返回值将作为value,这个value可以不写,但Spring内部解析时会变得复杂,使程序运行变慢
public String myModelAttribute1(@RequestParam String a) {
return a + " -> myModelAttribute1 -> test";
}
// 测试请求 Get localhost/test?a=1
@ResponseBody
@GetMapping("/test")
public String test(@ModelAttribute("a") String a, @ModelAttribute("b") String b) {
return a + "\n" + b;
}
@GetMapping("/page/test")
public String test(Model model) { // model 在页面模板上就可以使用
return "test"; // ViewName
}
}
// 第二种使用方式(配合@ControllerAdvice进行全局配置,这种方式只针对扫描到的Handler生效)
// 如果@ControllerAdvice不指定范围,则代表针对所有Handler生效
@ControllerAdvice // (basePackages = "com.demo.i.controller")
public class MyControllerAdvice {
@ModelAttribute
public void myModelAttribute() {
// do something...
}
}
1.4.3.2.2 @SessionAttribute 和 @SessionAttributes
根据源码,可以看出来
@SessionAttributes
是要加在Handler上来使用的。
@SessionAttributes
用于在Session中存储Model内部的数据,@SessionAttribute
则用于获取存储在Session中的数据。
@Controller
@SessionAttributes(names = {"a", "b"})
public class MyController {
@RequestMapping("/test1")
public String test1(ModelMap modelMap, HttpSession session) {
modelMap.addAttribute("a", "1");
modelMap.addAttribute("b", "2");
session.setAttribute("c", "10");
return "redirect:/test2";
}
@RequestMapping("/test2")
public String test2(ModelMap modelMap, HttpSession session) {
// ModelMap 中可以获取到 @SessionAttributes 注解中标记的属性,但获取不到直接存入Session中的属性
System.out.println(modelMap.get("a")); // 1
System.out.println(modelMap.get("b")); // 2
System.out.println(modelMap.get("c")); // null
// 可以直接通过Session获取到@SessionAttributes存储的属性
System.out.println(session.getAttribute("a")); // 1
System.out.println(session.getAttribute("b")); // 2
System.out.println(session.getAttribute("c")); // 10
return "redirect:/test3";
}
@RequestMapping("/test3")
public String test3(@SessionAttribute("a") String a,
@SessionAttribute("c") String c,
SessionStatus sessionStatus) {
// @SessionAttribute 注解可以获取到Session内的数据
System.out.println(a); // 1
System.out.println(c); // 10
// 清除 @SessionAttributes 注解标记的属性,对应的数据会从Session中删除
sessionStatus.setComplete();
return "redirect:/test4";
}
@RequestMapping("/test4")
public String test4(HttpSession session) {
// 因为上一个请求中已经清除了 @SessionAttributes 对属性的标记,所以a和b已经被删除了
System.out.println(session.getAttribute("a")); // null
System.out.println(session.getAttribute("b")); // null
System.out.println(session.getAttribute("c")); // 10
return "test4"; // ViewName
}
}
补充,有一个
@RequestAttribute
注解,它干的活和@SessionAttribute
一样,只不过它的作用范围是RequestScope
。
1.4.3.3 对ModelAndViewContainer对象的进一步组装
1.4.3.3.1 将FlashMap中的属性存入Model中
// 取出前文存放到request中的flashMap,然后存放到mavContainer里持有的的Model中
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
在上面的
RequestMappingHandlerAdapter#invokeHandlerMethod
源码里有如上这么一行代码,包括本文的上面也有对FlashMapManager
的处理,再结合我们之前在Spring的启动过程一文中说过的FlashMap
的用法,这里进行一下补充。
@PostMapping("/a")
public void a(HttpServletRequest request, HttpServletResponse response) throws IOException {
FlashMap flashMap = RequestContextUtils.getOutputFlashMap(request);
flashMap.put("a", "1");
flashMap.put("b", "2");
String redirect = "/b";
// 将 FlashMap 交由 FlashMapManager 管理
RequestContextUtils.saveOutputFlashMap(redirect, request, response);
response.sendRedirect(redirect);
}
@GetMapping("/b")
public String b(HttpServletRequest request, ModelMap modelMap) {
Map inputFlashMap = RequestContextUtils.getInputFlashMap(request);
Object returnValue = null;
if (inputFlashMap != null) {
returnValue = inputFlashMap.get("a");
}
return (String) returnValue;
}
相对于之前提供的写法,这里只是在b方法上添加了一个
ModelMap
入参。
这里要补充的就是,根据上面的源码的意思,可以在目标方法上使用Model
接收到FlashMap
内的数据。
1.4.3.3.2 将和@SessionAttributes和@ModelAttribute相关的属性存入Model中
// ModelFactory
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
throws Exception {
// 从 Session 中获取 @SessionAttributes 中指定的属性集
Map sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
// 合并属性,将 sessionAttributes 内的数据也添加到 Model 中
container.mergeAttributes(sessionAttributes);
// 运行 @ModelAttribute 注解注释的方法,如果有返回值 且 binding = true,则将返回值添加到 Model 中。
invokeModelAttributeMethods(request, container);
// 找到所有被 @ModelAttribute 注解 同时 又被 @SessionAttributes 指定的参数
for (String name : findSessionAttributeArguments(handlerMethod)) {
// 如果 Model 中不包含这个属性,就尝试从 Session 中获取这个属性然后添加到 Model 中,如果 Session 里面也没有这个参数就直接报错。
if (!container.containsAttribute(name)) {
Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
if (value == null) {
throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
}
container.addAttribute(name, value);
}
}
}
// ModelFactory
private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container)
throws Exception {
// modelMethods 内容的填充是在 RequestMappingHandlerAdapter#getModelFactory 方法的最后一行创建 ModelFactory 对象实例时进行的。
// 它实际上存放的就是被 @ModelAttribute 注解注释的方法。
while (!this.modelMethods.isEmpty()) {
// 获取 @ModelAttribute 注解的方法
InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();
// 获取到 @ModelAttribute 注解
ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
Assert.state(ann != null, "No ModelAttribute annotation");
// 判断 @ModelAttribute 中设置的 name/value 值是否在 Model 中存在
if (container.containsAttribute(ann.name())) {
// 这个是 binding = false 时的情况,默认情况下 binding = true,这里不具体说了,意思很明确了
if (!ann.binding()) {
container.setBindingDisabled(ann.name());
}
continue;
}
// 这下面是处理 binding = true 的情况
// 调用这个被 @ModelAttribute 注解注释的方法
Object returnValue = modelMethod.invokeForRequest(request, container);
if (!modelMethod.isVoid()){
// 获取到 @ModelAttribute 注解设置的value值;如果没有设置,则会通过反射将返回值的类型解析为value
// 所以还是尽量写上value值
String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
if (!ann.binding()) {
container.setBindingDisabled(returnValueName);
}
// 如果 Model 中没有这个属性,则将该属性添加到 Model 中
if (!container.containsAttribute(returnValueName)) {
container.addAttribute(returnValueName, returnValue);
}
}
}
}
上面的代码中提供了一种
@SessionAttributes
和@ModelAttribute
结合起来的使用方法,示例代码如下。
// 都是一些花里胡哨的用法。
@RestController
@SessionAttributes("test")
public class Test {
@ModelAttribute
public void a(HttpSession session) {
session.setAttribute("test", "123");
}
@RequestMapping("/test1")
public String test1(@ModelAttribute("test") String test) {
return test;
}
}
1.4.3.4 执行请求处理方法并处理返回值
// ServletInvocableHandlerMethod
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// ----------------------------------------- 执行请求处理方法 -----------------------------------------
// 这里面主要就是使用参数解析器对方法入参的解析,解析完参数后直接使用反射调用请求处理方法,这里就不细说了,感兴趣自己看
// 其中也会使用到在在之前就已经准备好的 @InitBinder 注解的相关的方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
// ----------------------------------------- 设置响应状态 -------------------------------------------
// 通过 @ResponseStatus 方式主动设置
setResponseStatus(webRequest);
if (returnValue == null) { // 判断请求值是否为null
/* 这里有3个条件,只要有一个达成就行
* 第一个和 Get 类型请求的缓存有关,如果符合缓存情况整个判断就为true。
* 第二个和 @ResponseStatus 注解有关,如果有这个注解整个判断就为true。
* 第三个 用来在ModelAndView中表示请求已经执行完成
*/
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) { // 判断 @ResponseStatus 有没有设置 reason 值,如果设置了,就不需要处理返回值了。
mavContainer.setRequestHandled(true); // 标记请求已经处理完成。
return;
}
// ----------------------------------------- 处理 请求处理方法的返回值 ------------------------------------
// 如果请求处理方法有返回值且没有使用@ResponseStatus注解,就需要走下面的逻辑
mavContainer.setRequestHandled(false); // 标记请求尚为完成处理
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
// 从 returnValueHandlers 中找到适合处理当前返回值的 返回值处理器,将返回值交由返回值处理器处理
// 不同的返回值处理器中 会在各自合适的时机将 ModelAndViewContainer 中的 RequestHandled 设置为true
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
returnValueHandlers
也就是返回值处理器。
如果使用了方法直接或间接使用了@ResponseBody
注解,则使用RequestResponseBodyMethodProcessor
来处理返回值,通过I/O写入response。
如果返回值使用ResponseEntity
,则使用HttpEntityMethodProcessor
来处理返回值。
里面有支持异步返回值的返回值处理器,例如AsyncTaskMethodReturnValueHandler
、CallableMethodReturnValueHandler
、DeferredResultMethodReturnValueHandler
。
补充: 如果方法没有直接或间接使用
@ResponseBody
注解,那返回值则是有严格限制的,可以试试返回int类型的值,Spring MVC会找不到合适的处理器而相应请求处理失败。
1.4.3.5 收尾工作
收尾工作做了如下几方面的工作。
1.对@SessionAttributes
注解的处理
2.组装ModelAndView
对象
3.对FlashMap
的处理
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
// -------------------------- 对 @SessionAttributes 注解的处理 -----------------------------
// 将列为 @SessionAttributes 的 Model 属性升级到会话级别,必要时添加 BindingResult 属性。
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) { // 如果请求已经处理完成
return null;
}
ModelMap model = mavContainer.getModel();
// ------------------------- 通过 ModelAndViewContainer 构建 ModelAndView --------------------
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView()); // 设置视图
}
// ------------------------- 处理 FlashMap 相关内容 -----------------------------
// 判断 Model 是否使 RedirectAttributes 类型,这个东西和 FlashMap 的使用有关。
if (model instanceof RedirectAttributes) {
Map flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
在上一篇说明Spring MVC框架启动过程的文章里,已经说了一种我比较喜欢的
FlashMap
使用方法。
在这里,从上面的源码中可以看到另一种FlashMap
的使用方式,示例代码如下展示。
@GetMapping("/test1")
public String test1(RedirectAttributes attributes) {
attributes.addFlashAttribute("a", "10");
return "redirect:/test2";
}
@ResponseBody
@GetMapping("/test2")
public Object test2(ModelMap model) {
return model.getAttribute("a");
}
1.4.4 处理最终的视图
// DispatcherServlet
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
boolean errorView = false;
// 如果存在异常,就尝试获取异常相关的view
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) {
mappedHandler.triggerAfterCompletion(request, response, null);
}
}
// DispatcherServlet
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// -------------------------------------- 处理 Locale ------------------------------
// 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) {
// 如果存在ViewName,就通过合适的ViewResolver解析出来一个View。
// 通常我们使用模板引擎时会配置一个ViewResolver以及一个特定的View
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) {
// 如果没有合适的ViewResolver,则会出现如下异常,通常出现这个异常意味着我们模板引擎没有配置好。
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// 如果我们在请求处理方法返回的时候直接创建好了View对象,则会进入这里。
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对象的类型是不同的,往往是模板引擎的依赖内自带的,通过它们内部的细节进行页面渲染。
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}
1.5 Spring MVC中的异步请求的逻辑
要想理解上面源码中的异步请求的意义,首先要知道异步请求的逻辑是如何运行的,之后再回头看上面的代码,才能更好的理解。
这部分逻辑主要涉及4个类,WebAsyncManager
、AsyncWebRequest
、AsyncContext
、AsyncListener
。
AsyncWebRequest
使用的是StandardServletAsyncWebRequest
作为具体实现。
AsyncContext
这个接口是Servlet
的技术。
它们的关系是这样的:WebAsyncManager
类中持有一个StandardServletAsyncWebRequest
的对象实例,而StandardServletAsyncWebRequest
内部又是对AsyncContext
接口的调用。
ServletRequest API文档
AsyncContext API文档
AsyncListener API文档
1.5.1 异步返回值处理器
可以通过
返回值处理器
来作为理解这一块逻辑的起点,Spring MVC中异步返回值常用的有Callable
、DeferredResult
、StreamingResponseBody
等。
这里以Callable
为例进行说明,可以找到它的返回值处理器,也就是CallableMethodReturnValueHandler
。
// 使用示例
// web.xml
true
@ResponseBody
@GetMapping("/test")
public Callable test() {
return () -> {
Thread.sleep(1000 * 2);
return "test"; // 当使用 Callable 时这里也可以返回 ViewName
};
}
// Callable类型返回值的处理器
public class CallableMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// 对 Callable 返回值的支持
return Callable.class.isAssignableFrom(returnType.getParameterType());
}
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
// returnValue 就是 请求处理方法 的返回值
Callable> callable = (Callable>) returnValue;
// 这里 getAsyncManager,会从 request 中获取之前存储的 WebAsyncManager,随和的startCallableProcessing才是主要逻辑
WebAsyncUtils.getAsyncManager(webRequest).startCallableProcessing(callable, mavContainer);
}
}
1.5.2 WebAsyncManager#startCallableProcessing
要想全面的理解,这里要结合
RequestMappingHandlerAdapter#invokeHandlerMethod
方法的部分代码片段来看。
// RequestMappingHandlerAdapter#invokeHandlerMethod 内的代码片段
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); // 注册 Callable 类型返回值的拦截器,可以在Xml文件中配置
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
// WebAsyncManager
public void startCallableProcessing(Callable> callable, Object... processingContext) throws Exception {
Assert.notNull(callable, "Callable must not be null");
// 这里会把 Callable 包装成 WebAsyncTask,请求处理方法 的返回值实际上可以直接是 WebAsyncTask 类型。
startCallableProcessing(new WebAsyncTask(callable), processingContext);
}
// WebAsyncManager
public void startCallableProcessing(final WebAsyncTask> webAsyncTask, Object... processingContext)
throws Exception {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
// 这里由于返回值类型是Callable,所以这里这个Timeout是空的,可以直接使用WebAsyncTask作为返回值,这样就能具体设置超时时间
Long timeout = webAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
}
// 这里由于返回值类型是Callable,所以这里这个Executor是空的,可以直接使用WebAsyncTask作为返回值,这样就能具体设置Executor的实现
AsyncTaskExecutor executor = webAsyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
}
else {
logExecutorWarning();
}
// ------------------------------------------------- 整合Callable处理拦截器 -------------------------------------------------
List interceptors = new ArrayList<>();
// 这个比较特殊,是在 WebAsyncTask#getInterceptor() 方法中定义的一个匿名内部类
interceptors.add(webAsyncTask.getInterceptor());
// 在 RequestMappingHandlerAdapter#invokeHandlerMethod 中有批量注册,但默认没有值,用的时候需要自己配置
// 在 FrameworkServlet#processRequest 中注册了一个 RequestBindingInterceptor
interceptors.addAll(this.callableInterceptors.values());
// timeoutCallableInterceptor 是 TimeoutCallableProcessingInterceptor 类的实例对象
interceptors.add(timeoutCallableInterceptor);
final Callable> callable = webAsyncTask.getCallable();
// 将拦截器集合封装成一个CallableInterceptorChain实例对象。
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);
// ------------------------------------------------ 添加一系列的行为处理器 -------------------------------------------
// ------------------------ 这些行为处理器的逻辑会在AsyncListener的回调中被执行,是一个重要的逻辑区域。 ----------------------
// 3(指在这个方法中发挥作用的顺序)
// 执行超时后的处理器 (#1.5.4 节可以看到调用)
this.asyncWebRequest.addTimeoutHandler(() -> {
logger.debug("Async request timeout for " + formatRequestUri());
Object result = interceptorChain.triggerAfterTimeout(this.asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
});
// 执行异常后的处理器 (#1.5.4 节可以看到调用)
this.asyncWebRequest.addErrorHandler(ex -> {
logger.debug("Async request error for " + formatRequestUri() + ": " + ex);
Object result = interceptorChain.triggerAfterError(this.asyncWebRequest, callable, ex);
result = (result != CallableProcessingInterceptor.RESULT_NONE ? result : ex);
setConcurrentResultAndDispatch(result);
});
// 执行完成后的处理器 (#1.5.4 节可以看到调用)
this.asyncWebRequest.addCompletionHandler(() ->
interceptorChain.triggerAfterCompletion(this.asyncWebRequest, callable));
// ----------------------------------- 调用拦截器的 beforeConcurrentHandling 方法 -----------------------------
interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable);
// ------------------------------------ 将Servlet容器委派的请求转换为异步模式 ------------------------------------------
// 1(指在这个方法中发挥作用的顺序)
startAsyncProcessing(processingContext);
// ----------------------------------- 对 请求处理方法的返回值 进行再包装 -------------------------------------------
// 将 Callable 类型的返回值封装为 Future,然后提交给 taskExecutor 等待执行。
try {
// 2(指在这个方法中发挥作用的顺序)
Future> future = this.taskExecutor.submit(() -> {
Object result = null;
try {
// ---------------------------- 调用拦截器的 preProcess 方法(正序遍历拦截器) -----------------------------
interceptorChain.applyPreProcess(this.asyncWebRequest, callable);
// ---------------------------- 执行返回的 请求处理方法 的返回值 ----------------------------------------
// 这个方法如果执行时间太久导致请求响应超过了指定时间,就会执行超时的逻辑
result = callable.call();
}
catch (Throwable ex) { // 这里是可以响应中断逻辑的,当超时后或Future执行异常后,程序会对的线程进行中断
result = ex;
}
finally {
// ---------------------------- 调用拦截器的 postProcess 方法(倒序遍历拦截器) -----------------------------
result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, result);
}
// 处理异步请求结果 并且 通过调用 AsyncContext#dispatch 来对当前请求的URI再次调度 (意思就是会对当前URI再发送一个请求)
// 能走到这里就代表 异步操作 是能完成的,代表会执行完成的逻辑。
setConcurrentResultAndDispatch(result);
});
// 设置的这个 TaskFuture,在之后的超时和异常处理中有用到。
interceptorChain.setTaskFuture(future);
}
catch (RejectedExecutionException ex) {
Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
setConcurrentResultAndDispatch(result);
throw ex;
}
}
1.5.3 将请求转换为异步模式
// WebAsyncManager
private void startAsyncProcessing(Object[] processingContext) {
synchronized (WebAsyncManager.this) {
// concurrentResult 是用来存放最后的处理结果的,RESULT_NONE 代表没有结果
this.concurrentResult = RESULT_NONE;
// 这里这个东西,上面传过来的是ModelAndViewContainer
this.concurrentResultContext = processingContext;
}
// ------------------------------------ 将请求转换为异步模式 ------------------------------------------
this.asyncWebRequest.startAsync();
if (logger.isDebugEnabled()) {
logger.debug("Started async request");
}
}
// StandardServletAsyncWebRequest
public void startAsync() {
Assert.state(getRequest().isAsyncSupported(),
"Async support must be enabled on a servlet and for all filters involved " +
"in async request processing. This is done in Java code using the Servlet API " +
"or by adding \"true \" to servlet and " +
"filter declarations in web.xml.");
Assert.state(!isAsyncComplete(), "Async processing has already completed");
// 如果当前请求已经调用过 startAsync,则直接返回,防止形成嵌套式调用。
if (isAsyncStarted()) {
return;
}
// 这里才是核心点,使用`startAsync`、`addListener`、`setTimeout`这些`Servlet`体系提供的方法,
// 来完成Spring MVC框架的异步请求的逻辑
this.asyncContext = getRequest().startAsync(getRequest(), getResponse()); // 将当前请求转换为异步模式
this.asyncContext.addListener(this); // 这里要求传入的是一个 AsyncListener 接口的实现类,接口内定义了4个回调方法。
if (this.timeout != null) {
this.asyncContext.setTimeout(this.timeout); // 整个异步请求的超时时间,由Servlet容器管理,超时后会回调超时的回调方法。
}
}
public boolean isAsyncStarted() {
// asyncContext 不为空代表请求已经转换为了异步模式。
// isAsyncStarted 则是检查此请求是否已调用 startAsync 方法使其进入异步模式。
return (this.asyncContext != null && getRequest().isAsyncStarted());
}
1.5.4 AsyncListener接口的实现类
// 这个参数要记住,因为在上面的源码中,有很多判断这个的,这个是用来判断异步请求是否已经完成的。
private AtomicBoolean asyncCompleted = new AtomicBoolean(false);
// 下面就是说的那4个回调函数
// StandardServletAsyncWebRequest
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
// StandardServletAsyncWebRequest
@Override
public void onError(AsyncEvent event) throws IOException {
// 调用在 WebAsyncManager#startCallableProcessing 中添加的处理器
this.exceptionHandlers.forEach(consumer -> consumer.accept(event.getThrowable()));
}
// StandardServletAsyncWebRequest
@Override
public void onTimeout(AsyncEvent event) throws IOException {
// 调用在 WebAsyncManager#startCallableProcessing 中添加的处理器
this.timeoutHandlers.forEach(Runnable::run);
}
// StandardServletAsyncWebRequest
@Override
public void onComplete(AsyncEvent event) throws IOException {
// 调用在 WebAsyncManager#startCallableProcessing 中添加的处理器
this.completionHandlers.forEach(Runnable::run);
this.asyncContext = null; // 当请求执行完成后,这里对 asyncContext 清空以契合之前的逻辑
this.asyncCompleted.set(true); // 重点记忆
}
在了解了这些方法的基础上进行一些较为重要的补充
如果超时,触发顺序是onTimeout
->onComplete
。
手动调用AsyncContext#complete()
,触发的顺序是onComplete
。
手动调用AsyncContext#dispatch()
,触发的顺序是dispatch
->onComplete
。
这里的dispatch
指的是对当前请求的URI再次调度。
而且dispatch
发生时使用的线程和AsyncListener
回调使用的线程是同一个。
记住这些内容在理解下文时很重要。
1.5.5 WebAsyncManager#setConcurrentResultAndDispatch
// WebAsyncManager
private void setConcurrentResultAndDispatch(Object result) {
synchronized (WebAsyncManager.this) {
// concurrentResult 代表最终的处理结果,如果 != RESULT_NONE,则代表已经处理过异步请求结果了。
if (this.concurrentResult != RESULT_NONE) {
return;
}
this.concurrentResult = result;
}
// 如果异步请求已经执行完成了的,就直接返回,可以看到它访问的就是我们上面刚刚看到过的 asyncCompleted。
// 防止对同一个请求的结果进行多次处理。
if (this.asyncWebRequest.isAsyncComplete()) {
if (logger.isDebugEnabled()) {
logger.debug("Async result set but request already complete: " + formatRequestUri());
}
return;
}
if (logger.isDebugEnabled()) {
boolean isError = result instanceof Throwable;
logger.debug("Async " + (isError ? "error" : "result set") + ", dispatch to " + formatRequestUri());
}
// 间接调用 AsyncContext#dispatch 来对当前请求的URI再次调度 (意思就是会对当前URI再发送一个请求)。
this.asyncWebRequest.dispatch();
}
// StandardServletAsyncWebRequest
public boolean isAsyncComplete() {
return this.asyncCompleted.get();
}
isAsyncComplete
的工作逻辑
由于AsyncContext#dispatch()
会导致再次调度当前请求的URI,
加上上面也说过的,再次调度时使用的线程与回调方法时使用的线程是同一个,
所以asyncCompleted
的值,对当前是准确有效的,这就是isAsyncComplete的工作逻辑。
asyncCompleted
表示了当前异步请求是否正常执行通过,所以在上面很多地方都有使用isAsyncComplete()
进行判断。
1.5.6 执行完成的逻辑
基于上面这些知识点,这里在梳理一下
处理完成的逻辑
。
第一步:Spring MVC 检测到某个请求处理方法的返回值是Callable类型的返回值
。
第二步:选用CallableMethodReturnValueHandler
作为返回值处理器
。
第三步:将当前请求转为异步模式
,同时注册一个自定义监听器。
第四步:在当前请求内创建一个线程任务,随后提交给taskExecutor
调度,然后这个线程任务顺利执行完成。
第五步:随着AsyncContext#dispatch()
方法的调用,Servlet容器会再次调度当前请求的URI,然后回调我们监听器内部的onComplete(AsyncEvent)
函数。
第六步:在onComplete(AsyncEvent)
内调用执行完成后的处理器
,执行拦截器的afterCompletion
方法。
第四步里的这个线程任务内主要包含的逻辑有:
1.执行请求处理方法返回的 Callable.
2.调用`AsyncContext#dispatch()`,通知Servlet容器对请求进行再调度。
截至到第四步这里,这些步骤都是在首次请求的线程中进行的业务逻辑。
从第五步开始,之后的步骤都是在新的线程内完成的。
1.5.7 执行超时的逻辑
基于上面这些知识点,这里在梳理一下
超时的逻辑
。
第一步:Spring MVC 检测到某个请求处理方法的返回值是Callable类型的返回值
。
第二步:选用CallableMethodReturnValueHandler
作为返回值处理器
。
第三步:将当前请求转为异步模式
,同时注册一个自定义监听器。
第四步:在当前请求内创建一个线程任务,随后提交给taskExecutor
调度,但由于Callable
执行过久导致请求超时。
第五步:Servlet容器会再次调度当前请求的URI,然后首先回调我们监听器内部的onTimeout(AsyncEvent)
,然后调用onComplete(AsyncEvent)
。
第六步:在onTimeout(AsyncEvent)
内调用执行超时后的处理器
,在onComplete(AsyncEvent)
内调用执行完成后的处理器
。
执行超时的逻辑 和 执行完成的逻辑,它们的差别点开始于第四步的 Callable 能否在指定时间内执行完成。
能完成就代表异步操作已完成;完不成就代表异步操作已超时;
这里主要看下超时机制的处理逻辑。
FutureTask API文档
// WebAsyncManager#startCallableProcessing 代码片段
Future> future = this.taskExecutor.submit(() -> {
result = callable.call();
// ... 线程任务内的逻辑
});
interceptorChain.setTaskFuture(future);
// WebAsyncManager#startCallableProcessing 代码片段
this.asyncWebRequest.addTimeoutHandler(() -> {
logger.debug("Async request timeout for " + formatRequestUri());
Object result = interceptorChain.triggerAfterTimeout(this.asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
});
// CallableInterceptorChain
public Object triggerAfterTimeout(NativeWebRequest request, Callable> task) {
// 这里会调用Future#cancel,试图取消任务的执行。
cancelTask();
// 调用拦截器的超时方法,对返回值进行组装,默认情况下 result 会是一个 AsyncRequestTimeoutException 类型的异常。
// 可以看 TimeoutCallableProcessingInterceptor 这个拦截器得知原因。
for (CallableProcessingInterceptor interceptor : this.interceptors) {
try {
Object result = interceptor.handleTimeout(request, task);
if (result == CallableProcessingInterceptor.RESPONSE_HANDLED) {
break;
}
else if (result != CallableProcessingInterceptor.RESULT_NONE) {
return result;
}
}
catch (Throwable ex) {
return ex;
}
}
return CallableProcessingInterceptor.RESULT_NONE;
}
// CallableInterceptorChain
private void cancelTask() {
Future> future = this.taskFuture;
if (future != null) {
try {
// 会尝试取消这个任务的执行。
// 没什么好说的,已经在上面放了 API 文档,自己看。
future.cancel(true);
}
catch (Throwable ex) {
// Ignore
}
}
}