servlet_spring-mvc

​ 本文分析基于 基于spring-webmvc 4.0.3.RELEASE

springMVC 核心servlet —DispatcherServlet

整体结构

image-20181219135235499

Aware:如果某个类想使用Spring的一些东西,就可以实现Aware接口

Capable:具有spring某个类的能力

时序图预览

image-20200125155900180

创建过程

HttpServletBean的创建

​ 实现了EnvironmentCapableEnvironmentAware ,主要作用是将Servlet中配置的参数设置到相应的容器

FramewordkServlet的创建

​ 实现了ApplicationContextAware,其主要作用是调用initWebApplicationContext()初始化WebApplicationContext

  • 获取spring的根工期rootContext
  • 设置webApplicationContext并根据情况调用onRefresh方法
  • 将WebApplicationContext设置到ServletContext中

DispatcherServlet的创建

​ onRefresh方法是DispatcherServlet的入口方法,onRefresh中调用了initStrategies()方法来初始化一些策略组件

protected void onRefresh(ApplicationContext context) {
    this.initStrategies(context);
}

/**
 * Initialize the strategy objects that this servlet uses.
 * <p>May be overridden in subclasses in order to initialize further strategy objects.
 */
protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
}

如果没有配置相关,会使用默认的配置,在DispatcherServlet.properties文件中

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
   org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
   org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
   org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

Tips:If no bean is defined with the given name in the BeanFactory for this namespace, no multipart handling is provided.

组件概览

HandlerMapping

​ 它的作用是根据request找到对应的处理器handler和Interceptors ,首先看下接口信息

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
/**
 * Interface to be implemented by objects that define a mapping between
 * requests and handler objects.
 * @see org.springframework.core.Ordered
 * @see org.springframework.web.servlet.handler.AbstractHandlerMapping
 * @see org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping
 * @see org.springframework.web.servlet.handler.SimpleUrlHandlerMapping
 */
public interface HandlerMapping {

   String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";

   String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";

   String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";

   String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";

   String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";

   String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

   /**
    * Return a handler and any interceptors for this request. The choice may be made
    * on request URL, session state, or any factor the implementing class chooses.
    * <p>The returned HandlerExecutionChain contains a handler Object, rather than
    * even a tag interface, so that handlers are not constrained in any way.
    * For example, a HandlerAdapter could be written to allow another framework's
    * handler objects to be used.
    * <p>Returns {@code null} if no match was found. This is not an error.
    * The DispatcherServlet will query all registered HandlerMapping beans to find
    * a match, and only decide there is an error if none can find a handler.
    * @param request current HTTP request
    * @return a HandlerExecutionChain instance containing handler object and
    * any interceptors, or {@code null} if no mapping found
    * @throws Exception if there is an internal error
    */
   HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

以常用接口实现为例,RequestMappingHandlerMapping

image-20181219181838287

这里提供了Ordered接口,可以确定匹配的顺序,同时通过继承WebApplicationObjectSupport抽象类,可以获取相关Bean(handler),更多细节可查看 类AbstractHandlerMapping。

HandlerAdapter

​ 之所以要使用HandlerAdapter,是因为SpringMVC中并没有对处理器做任何的限制,可以是个类,方法(HandlerMethod)这点从hanlder是Object就可以看出,首先是接口实现

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * MVC framework SPI interface, allowing parameterization of core MVC workflow.
 *
 * <p>Interface that must be implemented for each handler type to handle a request.
 * This interface is used to allow the {@link DispatcherServlet} to be indefinitely
 * extensible. The DispatcherServlet accesses all installed handlers through this
 * interface, meaning that it does not contain code specific to any handler type.
 *
 * <p>Note that a handler can be of type {@code Object}. This is to enable
 * handlers from other frameworks to be integrated with this framework without
 * custom coding, as well as to allow for annotation handler objects that do
 * not obey any specific Java interface.
 *
 * <p>This interface is not intended for application developers. It is available
 * to handlers who want to develop their own web workflow.
 *
 * <p>Note: HandlerAdaptger implementators may implement the
 * {@link org.springframework.core.Ordered} interface to be able to specify a
 * sorting order (and thus a priority) for getting applied by DispatcherServlet.
 * Non-Ordered instances get treated as lowest priority.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter
 * @see org.springframework.web.servlet.handler.SimpleServletHandlerAdapter
 */
public interface HandlerAdapter {

   /**
    * Given a handler instance, return whether or not this HandlerAdapter can
    * support it. Typical HandlerAdapters will base the decision on the handler
    * type. HandlerAdapters will usually only support one handler type each.
    * <p>A typical implementation:
    * <p>{@code
    * return (handler instanceof MyHandler);
    * }
    * @param handler handler object to check
    * @return whether or not this object can use the given handler
    */
   boolean supports(Object handler);

   /**
    * Use the given handler to handle this request.
    * The workflow that is required may vary widely.
    * @param request current HTTP request
    * @param response current HTTP response
    * @param handler handler to use. This object must have previously been passed
    * to the {@code supports} method of this interface, which must have
    * returned {@code true}.
    * @throws Exception in case of errors
    * @return ModelAndView object with the name of the view and the required
    * model data, or {@code null} if the request has been handled directly
    */
   ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

   /**
    * Same contract as for HttpServlet's {@code getLastModified} method.
    * Can simply return -1 if there's no support in the handler class.
    * @param request current HTTP request
    * @param handler handler to use
    * @return the lastModified value for the given handler
    * @see javax.servlet.http.HttpServlet#getLastModified
    * @see org.springframework.web.servlet.mvc.LastModified#getLastModified
    */
   long getLastModified(HttpServletRequest request, Object handler);

}

其中一个实现类SimpleControllerHandlerAdapter

package org.springframework.web.servlet.mvc;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerAdapter;
import org.springframework.web.servlet.ModelAndView;

/**
 * Adapter to use the plain {@link Controller} workflow interface with
 * the generic {@link org.springframework.web.servlet.DispatcherServlet}.
 * Supports handlers that implement the {@link LastModified} interface.
 * <p>This is an SPI class, not used directly by application code.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.web.servlet.DispatcherServlet
 * @see Controller
 * @see LastModified
 * @see HttpRequestHandlerAdapter
 */
public class SimpleControllerHandlerAdapter implements HandlerAdapter {
   @Override
   public boolean supports(Object handler) {
      return (handler instanceof Controller);
   }
   @Override
   public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
      return ((Controller) handler).handleRequest(request, response);
   }

   @Override
   public long getLastModified(HttpServletRequest request, Object handler) {
      if (handler instanceof LastModified) {
         return ((LastModified) handler).getLastModified(request);
      }
      return -1L;
   }

}

一般默认使用的是 RequestMappingHandlerAdapter ,在其父类中有这样一句设置Order

private int order = Ordered.LOWEST_PRECEDENCE;

image-20181219180136155

HandlerAdapter implementators may implement the Order,使用时候是遍历 handlerAdapters ,调用supports判断直接返回(除了设置的,默认的实现有3个)。注意Order是越低优先级越高的。更多细节可以查看#afterPropertiesSet()方法

总结一下,主要做了三件事,解析参数、执行请求,处理返回结果

  • 解析参数的过程中用到的参数来源有多个,大体可分为两类
    • 一类是从Model来的(通过FlashMapManager和ModelFactory)
    • 另一类是从Request来的, 具体使用HandlerMethodArgumentResolver进行解析(有的是@InitBinder WebDataBinder)
  • 执行请求的是用HandlerMethod的子类ServletInvocableHandlerMethod
  • 返回值用HandlerMethodReturnValueHandler进行解析。

另外,整个处理过程中ModelAndViewContainer起着参数传递的作用。

​ 这里还有一个需要注意的,就是RequestMappingHandlerAdapter 是怎么注入的,通过debug发现,默认的实现HandlerAdapter 有 3个 ,除了前者还有HttpRequestHandlerAdapteSimpleControllerHandlerAdapte

通过查询官方文档发现,有两种方式, 会自动生成相关@RequestMapping的类。

  • @EnableWebMvc
  • 配置文件配置了mvc:annotation-driven

image-20200125161405442

官方文档截图

HandlerExceptionResolver

​ 根据异常设置ModelAndView,之后交给render方法去渲染,这里要注意它是在render之前工作,所以只作用于解析对请求做处理过程中的产生的异常。

package org.springframework.web.servlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * Interface to be implemented by objects than can resolve exceptions thrown
 * during handler mapping or execution, in the typical case to error views.
 * Implementors are typically registered as beans in the application context.
 *
 * <p>Error views are analogous to the error page JSPs, but can be used with
 * any kind of exception including any checked exception, with potentially
 * fine-granular mappings for specific handlers.
 *
 * @author Juergen Hoeller
 * @since 22.11.2003
 */
public interface HandlerExceptionResolver {
   /**
    * Try to resolve the given exception that got thrown during on handler execution,
    * returning a ModelAndView that represents a specific error page if appropriate.
    * <p>The returned ModelAndView may be {@linkplain ModelAndView#isEmpty() empty}
    * to indicate that the exception has been resolved successfully but that no view
    * should be rendered, for instance by setting a status code.
    * @param request current HTTP request
    * @param response current HTTP response
    * @param handler the executed handler, or {@code null} if none chosen at the
    * time of the exception (for example, if multipart resolution failed)
    * @param ex the exception that got thrown during handler execution
    * @return a corresponding ModelAndView to forward to,
    * or {@code null} for default processing
    */
   ModelAndView resolveException(
         HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);

}

其实现SimpleMappingExceptionResolver 可以通过扩展配置,设置自定义类型

image-20181219173520333

可以配合定制自定义Exception,前端统一展示等

ViewResolver

​ 通过viewName来找到 对应的View,View是用来渲染页面的,主要解决用什么模版和用什么规则填入参数

package org.springframework.web.servlet;
import java.util.Locale;

/**
 * Interface to be implemented by objects that can resolve views by name.
 *
 * <p>View state doesn't change during the running of the application,
 * so implementations are free to cache views.
 *
 * <p>Implementations are encouraged to support internationalization,
 * i.e. localized view resolution.
 *
 * @author Rod Johnson
 * @author Juergen Hoeller
 * @see org.springframework.web.servlet.view.InternalResourceViewResolver
 * @see org.springframework.web.servlet.view.ResourceBundleViewResolver
 * @see org.springframework.web.servlet.view.XmlViewResolver
 */
public interface ViewResolver {

   /**
    * Resolve the given view by name.
    * <p>Note: To allow for ViewResolver chaining, a ViewResolver should
    * return {@code null} if a view with the given name is not defined in it.
    * However, this is not required: Some ViewResolvers will always attempt
    * to build View objects with the given name, unable to return {@code null}
    * (rather throwing an exception when View creation failed).
    * @param viewName name of the view to resolve
    * @param locale Locale in which to resolve the view.
    * ViewResolvers that support internationalization should respect this.
    * @return the View object, or {@code null} if not found
    * (optional, to allow for ViewResolver chaining)
    * @throws Exception if the view cannot be resolved
    * (typically in case of problems creating an actual View object)
    */
   View resolveViewName(String viewName, Locale locale) throws Exception;

}

常用的jsp解析

image-20181219195241323

按照类图可以把继承AbstractCachingViewResolver(里面两个Map的方式值得学习)分成两类

  • ResourceBundleViewResolver 可以同时支持多种类型的视图,需要将每一个视图名和对应的视图类型配置到相应的properties文件中,该类的注释中,解释了其用法,
 /* <p>This {@code ViewResolver} supports localized view definitions,
 * using the default support of {@link java.util.PropertyResourceBundle}.
 * For example, the basename "views" will be resolved as class path resources
 * "views_de_AT.properties", "views_de.properties", "views.properties" -
 * for a given Locale "de_AT"./
  • UrlBasedViewResolver系列的解析器都是对单一视图进行解析的,只需找到模版就行,例如

InternalResourceViewResolver 是专门用来解析jsp,FreeMarkerViewResolver只针对FreeMarker

相关扩展ExecelViewResolver、SimpleMapViewResolver

默认实现是 InternalResourceViewResolver

RequestToViewNameTranslator

比较简单,当handler处理完后,没有View和ViewName的会调用该方法。

/**
 * Do we need view name translation?
 */
private void applyDefaultViewName(HttpServletRequest request, ModelAndView mv) throws Exception {
   if (mv != null && !mv.hasView()) {
      mv.setViewName(getDefaultViewName(request));
   }
}
/**
 * Translate the supplied request into a default view name.
 * @param request current HTTP servlet request
 * @return the view name (or {@code null} if no default found)
 * @throws Exception if view name translation failed
 */
protected String getDefaultViewName(HttpServletRequest request) throws Exception {
    return this.viewNameTranslator.getViewName(request);
}

​ 默认实现是RequestToViewNameTranslator

LocaleResolver

​ 用于从request中解析出Locale,默认实现是

package org.springframework.web.servlet.i18n;

import java.util.Locale;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.LocaleResolver;

/**
 * {@link LocaleResolver} implementation that simply uses the primary locale
 * specified in the "accept-language" header of the HTTP request (that is,
 * the locale sent by the client browser, normally that of the client's OS).
 *
 * <p>Note: Does not support {@code setLocale}, since the accept header
 * can only be changed through changing the client's locale settings.
 *
 * @author Juergen Hoeller
 * @since 27.02.2003
 * @see javax.servlet.http.HttpServletRequest#getLocale()
 */
public class AcceptHeaderLocaleResolver implements LocaleResolver {

   public Locale resolveLocale(HttpServletRequest request) {
      return request.getLocale();
   }

   public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
      throw new UnsupportedOperationException(
            "Cannot change HTTP accept header - use a different locale resolution strategy");
   }

}

​ 在DispatcherServlet#doSerive()方法中, request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); 放进request中,这里除了视图解析的时候需要用到,在使用国际化主题的时候也会用到

切换相关,可查看 LocaleChangeInterceptor

ThemeResolver

​ SpringMVC中和主题相关的类有如下

  • ThemeResolver 作用是从request中解析出主题名,默认实现FixedThemeResolver
  • ThemeSource 根据主题名找到对应的主题 ,默认使用WebApplicationContext
  • Theme 就是主题,包含了具体资源
package org.springframework.web.servlet.theme;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * {@link org.springframework.web.servlet.ThemeResolver} implementation
 * that simply uses a fixed theme. The fixed name can be defined via
 * the "defaultThemeName" property; out of the box, it is "theme".
 *
 * <p>Note: Does not support {@code setThemeName}, as the fixed theme
 * cannot be changed.
 *
 * @author Jean-Pierre Pawlak
 * @author Juergen Hoeller
 * @since 17.06.2003
 * @see #setDefaultThemeName
 */
public class FixedThemeResolver extends AbstractThemeResolver {
   @Override
   public String resolveThemeName(HttpServletRequest request) {
      return getDefaultThemeName();
   }

   @Override
   public void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName) {
      throw new UnsupportedOperationException("Cannot change theme - use a different theme resolution strategy");
   }

}

切换相关,可查看 ThemeChangeInterceptor

MultipartResolver

​ 用于处理文件上传,注意这个组件在DispatcherServlet.properties 中 没有默认实现,首先是接口

package org.springframework.web.multipart;

import javax.servlet.http.HttpServletRequest;

/**
 * A strategy interface for multipart file upload resolution in accordance
 * with <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC 1867</a>.
 * Implementations are typically usable both within an application context
 * and standalone.
 *
 * <p>There are two concrete implementations included in Spring, as of Spring 3.1:
 * <ul>
 * <li>{@link org.springframework.web.multipart.commons.CommonsMultipartResolver} for Jakarta Commons FileUpload
 * <li>{@link org.springframework.web.multipart.support.StandardServletMultipartResolver} for Servlet 3.0 Part API
 * </ul>
 *
 * @author Juergen Hoeller
 * @author Trevor D. Cook
 * @since 29.09.2003
 * @see MultipartHttpServletRequest
 * @see MultipartFile
 * @see org.springframework.web.multipart.commons.CommonsMultipartResolver
 * @see org.springframework.web.multipart.support.ByteArrayMultipartFileEditor
 * @see org.springframework.web.multipart.support.StringMultipartFileEditor
 * @see org.springframework.web.servlet.DispatcherServlet
 */
public interface MultipartResolver {

   /**
    * Determine if the given request contains multipart content.
    * <p>Will typically check for content type "multipart/form-data", but the actually
    * accepted requests might depend on the capabilities of the resolver implementation.
    * @param request the servlet request to be evaluated
    * @return whether the request contains multipart content
    */
   boolean isMultipart(HttpServletRequest request);

   /**
    * Parse the given HTTP request into multipart files and parameters,
    * and wrap the request inside a
    * {@link org.springframework.web.multipart.MultipartHttpServletRequest} object
    * that provides access to file descriptors and makes contained
    * parameters accessible via the standard ServletRequest methods.
    * @param request the servlet request to wrap (must be of a multipart content type)
    * @return the wrapped servlet request
    * @throws MultipartException if the servlet request is not multipart, or if
    * implementation-specific problems are encountered (such as exceeding file size limits)
    * @see MultipartHttpServletRequest#getFile
    * @see MultipartHttpServletRequest#getFileNames
    * @see MultipartHttpServletRequest#getFileMap
    * @see javax.servlet.http.HttpServletRequest#getParameter
    * @see javax.servlet.http.HttpServletRequest#getParameterNames
    * @see javax.servlet.http.HttpServletRequest#getParameterMap
    */
   MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

   /**
    * Cleanup any resources used for the multipart handling,
    * like a storage for the uploaded files.
    * @param request the request to cleanup resources for
    */
   void cleanupMultipart(MultipartHttpServletRequest request);

}

常用实现CommonsMultipartResolver

image-20181219214146380

对于上传类型判断是 multipart/form-data, 两种实现

  • 一种是Servlet的标准实现StandardServletMultipartResolver
  • 一种是Apache的commons-fileupload方式

FlashMapManager

​ FlashMap主要用于redirect中传递参数,FlashMapManage是用来管理FlashMap的

package org.springframework.web.servlet;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
 * A strategy interface for retrieving and saving FlashMap instances.
 * See {@link FlashMap} for a general overview of flash attributes.
 *
 * @author Rossen Stoyanchev
 * @since 3.1
 * @see FlashMap
 */
public interface FlashMapManager {

   /**
    * Find a FlashMap saved by a previous request that matches to the current
    * request, remove it from underlying storage, and also remove other
    * expired FlashMap instances.
    * <p>This method is invoked in the beginning of every request in contrast
    * to {@link #saveOutputFlashMap}, which is invoked only when there are
    * flash attributes to be saved - i.e. before a redirect.
    * @param request the current request
    * @param response the current response
    * @return a FlashMap matching the current request or {@code null}
    */
   FlashMap retrieveAndUpdate(HttpServletRequest request, HttpServletResponse response);

   /**
    * Save the given FlashMap, in some underlying storage and set the start
    * of its expiration period.
    * <p><strong>NOTE:</strong> Invoke this method prior to a redirect in order
    * to allow saving the FlashMap in the HTTP session or in a response
    * cookie before the response is committed.
    * @param flashMap the FlashMap to save
    * @param request the current request
    * @param response the current response
    */
   void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response);

}

​ 两个方法,分别是retrieveAndUpdate 用于恢复参数,saveOutputFlashMap用于保存参数

类图也比较简单

image-20181219214559988

默认实现是SessionFlashMapManager

整个redirects 的参数通过FlashMap传递的过程分三步

  1. 首先,RequestMappingHandlerAapter会将其设置到outputFlashMap中,如果是redirect类型的返回类型值(将需要传递的参数设置到outputFlashMap中,也可以是RedirectAttributes类型的参数中)
  2. 在RedirectView中会调用#saveOutputFlashMap()方法,将outputFlashMap中的参数设置到Session
  3. 请求redirect后,DispatcherServlet会调有你#retrieveAndUpdate()方法从Session中获取inputFlashMap并设置到Request的属性中备用,同时从Session中删除

处理请求

FrameworkServlet

​ Servlet的处理过程,首先是从Servlet接口的service,然后在HttpServlet的service方法中根据请求的类型不同将请求路由到了doGet、doHead等方法。

​ FrameworkServlet中重写了doGet等方法。将请求集中到processRequest方法进行统一处理

/**
 * Override the parent class implementation in order to intercept PATCH
 * requests.
 */
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    String method = request.getMethod();
    if (method.equalsIgnoreCase(RequestMethod.PATCH.name())) {
        processRequest(request, response);
    }
    else {
        super.service(request, response);
    }
}
/**
 * Delegate GET requests to processRequest/doService.
 * <p>Will also be invoked by HttpServlet's default implementation of {@code doHead},
 * with a {@code NoBodyResponse} that just captures the content length.
 * @see #doService
 * @see #doHead
 */
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response)
        throws ServletException, IOException {

    processRequest(request, response);
}

​ processRequest这个方法主要做了两件事情(当然还有doService,和对异步请求对处理)

  • 对LocaleContext(获取locale)和RequestAttributes(管理request和session属性)的设置及恢复
  • 处理完后发布ServletRequestHandledEvent消息

LocaleContextHolder 、RequestContextHolder

public abstract class LocaleContextHolder {
    private static final ThreadLocal localeContextHolder = new NamedThreadLocal("Locale context");
    private static final ThreadLocal inheritableLocaleContextHolder = new NamedInheritableThreadLocal("Locale context");
    ....
}

这里比较有意思的地方是这是个抽象类,里面的方法实现都是静态方法,只能调用不能实例化。RequestContextHolder的实现类似,封装的是ServletRequestAttributes

这里之所以需要对LocaleContext和RequestAttributes恢复,是因为在Servlet外面可能还有别等操作,例如Filter(Spring-MVC的HandlerInterceptor),为了不影响那些操作。

DispatcherServlet

​ DispatcherServlet是FrameworkServlet的子类,实现了doService方法

doService
/**
 * Exposes the DispatcherServlet-specific request attributes and delegates to {@link #doDispatch}
 * for the actual dispatching.
 */
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) 
    throws Exception {
    // Keep a snapshot of the request attributes in case of an include,
    // to be able to restore the original attributes after the include.
    Map<String, Object> attributesSnapshot = null;
    if (WebUtils.isIncludeRequest(request)) {
        attributesSnapshot = new HashMap<String, Object>();
        Enumeration<?> attrNames = request.getAttributeNames();
        while (attrNames.hasMoreElements()) {
            String attrName = (String) attrNames.nextElement();
            if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }
    }

    // Make framework objects available to handlers and view objects.
    request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
    request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

    //Pass paramter use to Redirect
    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()) {
            return;
        }
        // Restore the original attribute snapshot, in case of an include.
        if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
        }
    }
}

在doDispatch之前做了

  • 判断是否include请求,是的话保存快照(attributesSnapshot)
  • 为request设置默认的属性,在后面的handlers和view中需要使用
doDispatch

下面是doDispatch的方法的实现,主要的功能是 Process the actual dispatching to the handler.

/**
 * Process the actual dispatching to the handler.
 * <p>The handler will be obtained by applying the servlet's HandlerMappings in order.
 * The HandlerAdapter will be obtained by querying the servlet's installed HandlerAdapters
 * to find the first that supports the handler class.
 * <p>All HTTP methods are handled by this method. It's up to HandlerAdapters or handlers
 * themselves to decide which methods are acceptable.
 * @param request current HTTP request
 * @param response current HTTP response
 * @throws Exception in case of any kind of processing failure
 */
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) 
    throws Exception {
    HttpServletRequest processedRequest = request;
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        ModelAndView mv = null;
        Exception dispatchException = null;

        try {
            processedRequest = checkMultipart(request);
            multipartRequestParsed = processedRequest != request;

            // Determine handler for the current request.
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null || mappedHandler.getHandler() == null) {
                noHandlerFound(processedRequest, response);
                return;
            }

            // Determine handler adapter for the current request.
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

            // Process last-modified header, if supported by the handler.
            String method = request.getMethod();
            boolean isGet = "GET".equals(method);
            if (isGet || "HEAD".equals(method)) {
                long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                if (logger.isDebugEnabled()) {
                    logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
                }
                if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                    return;
                }
            }

            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }

            try {
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            }
            finally {
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
            }
            applyDefaultViewName(request, mv);
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }catch (Exception ex) {
            dispatchException = ex;
        }
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }catch (Exception ex) {
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    }catch (Error err) {
        triggerAfterCompletionWithError(processedRequest, response, mappedHandler, err);
    }finally {
        if (asyncManager.isConcurrentHandlingStarted()) {
            // Instead of postHandle and afterCompletion
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            return;
        }
        // Clean up any resources used by a multipart request.
        if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
        }
    }
}

整个处理过程最核心的代码只有4句

  • 根据request请求在HandlerMapping找到对应的Handler
  • 根据Handler找到对应的HandlerAdapter
  • 用HandlerAdapter处理Handler,返回一个ModelAndView(Controller就是在这个地方执行)
  • 根据ModelAndView的信息(或异常处理),通过ViewResolver找到View,并根据Model中的数据渲染输出后给用户

整个#doDispatcher()处理流程如下

image-20181219111943655

​ 从请求开始的话,当请求到达服务器,服务器(Tomcat connector)分配一个socket线程来连接,创建request和response ,然后交给对应的servlet处理(中间经过Pipeline,Filter),这样请求就从容器(Tomcat)到Servlet(springMVC)

异步请求

​ http协议是单向的,只能客户端自己拉,不能服务端主动推。

异步请求一般有两种方式,定时轮询或长链接。Servlet对异步的支持是通过长链接的方式。

servlet异步支持

ServletRequest

​ 首先是在 3.0后的 ServletRequest 中可以找到#startAsync() 返回一个异步容器

package javax.servlet;

import java.io.*;
import java.util.*;

/**
 * Defines an object to provide client request information to a servlet.  The
 * servlet container creates a <code>ServletRequest</code> object and passes
 * it as an argument to the servlet's <code>service</code> method.
 *
 * <p>A <code>ServletRequest</code> object provides data including
 * parameter name and values, attributes, and an input stream.
 * Interfaces that extend <code>ServletRequest</code> can provide
 * additional protocol-specific data (for example, HTTP data is
 * provided by {@link javax.servlet.http.HttpServletRequest}.
 * 
 * @author 	Various
 *
 * @see 	javax.servlet.http.HttpServletRequest
 *
 */
public interface ServletRequest {
    /**
     * Puts this request into asynchronous mode, and initializes its
     * {@link AsyncContext} with the original (unwrapped) ServletRequest
     * and ServletResponse objects.
     *
     * <p>Calling this method will cause committal of the associated
     * response to be delayed until {@link AsyncContext#complete} is
     * called on the returned {@link AsyncContext}, or the asynchronous
     * operation has timed out.
     *
     * <p>Calling {@link AsyncContext#hasOriginalRequestAndResponse()} on
     * the returned AsyncContext will return <code>true</code>. Any filters
     * invoked in the <i>outbound</i> direction after this request was put
     * into asynchronous mode may use this as an indication that any request
     * and/or response wrappers that they added during their <i>inbound</i>
     * invocation need not stay around for the duration of the asynchronous
     * operation, and therefore any of their associated resources may be
     * released.
     *
     * <p>This method clears the list of {@link AsyncListener} instances
     * (if any) that were registered with the AsyncContext returned by the
     * previous call to one of the startAsync methods, after calling each
     * AsyncListener at its {@link AsyncListener#onStartAsync onStartAsync}
     * method.
     *
     * <p>Subsequent invocations of this method, or its overloaded 
     * variant, will return the same AsyncContext instance, reinitialized
     * as appropriate.
     *
     * @return the (re)initialized AsyncContext
     * 
     * @throws IllegalStateException if this request is within the scope of
     * a filter or servlet that does not support asynchronous operations
     * (that is, {@link #isAsyncSupported} returns false),
     * or if this method is called again without any asynchronous dispatch
     * (resulting from one of the {@link AsyncContext#dispatch} methods),
     * is called outside the scope of any such dispatch, or is called again
     * within the scope of the same dispatch, or if the response has
     * already been closed
     *
     * @since Servlet 3.0
     */
    public AsyncContext startAsync() throws IllegalStateException;
    //...
}

异步容器AsyncContext

package javax.servlet;

/**
 * Class representing the execution context for an asynchronous operation
 * that was initiated on a ServletRequest.
 *
 * <p>An AsyncContext is created and initialized by a call to
 * {@link ServletRequest#startAsync()} or
 * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)}.
 * Repeated invocations of these methods will return the same AsyncContext
 * instance, reinitialized as appropriate.
 *
 * <p>In the event that an asynchronous operation has timed out, the
 * container must run through these steps:
 * <ol>
 * <li>Invoke, at their {@link AsyncListener#onTimeout onTimeout} method, all
 * {@link AsyncListener} instances registered with the ServletRequest
 * on which the asynchronous operation was initiated.</li>
 * <li>If none of the listeners called {@link #complete} or any of the
 * {@link #dispatch} methods, perform an error dispatch with a status code
 * equal to <tt>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</tt>.</li>
 * <li>If no matching error page was found, or the error page did not call
 * {@link #complete} or any of the {@link #dispatch} methods, call
 * {@link #complete}.</li>
 * </ol>
 *
 * @since Servlet 3.0
 */
public interface AsyncContext {

    /**
     * The name of the request attribute under which the original
     * request URI is made available to the target of a
     * {@link #dispatch(String)} or {@link #dispatch(ServletContext,String)} 
     */
    static final String ASYNC_REQUEST_URI = "javax.servlet.async.request_uri";

    /**
     * The name of the request attribute under which the original
     * context path is made available to the target of a
     * {@link #dispatch(String)} or {@link #dispatch(ServletContext,String)} 
     */
    static final String ASYNC_CONTEXT_PATH = "javax.servlet.async.context_path";

    /**
     * The name of the request attribute under which the original
     * path info is made available to the target of a
     * {@link #dispatch(String)} or {@link #dispatch(ServletContext,String)} 
     */
    static final String ASYNC_PATH_INFO = "javax.servlet.async.path_info";

    /**
     * The name of the request attribute under which the original
     * servlet path is made available to the target of a
     * {@link #dispatch(String)} or {@link #dispatch(ServletContext,String)}  
     */
    static final String ASYNC_SERVLET_PATH = "javax.servlet.async.servlet_path";

    /**
     * The name of the request attribute under which the original
     * query string is made available to the target of a
     * {@link #dispatch(String)} or {@link #dispatch(ServletContext,String)} 
     */
    static final String ASYNC_QUERY_STRING = "javax.servlet.async.query_string";


    /**
     * Gets the request that was used to initialize this AsyncContext
     * by calling {@link ServletRequest#startAsync()} or
     * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)}.
     *
     * @return the request that was used to initialize this AsyncContext
     */
    public ServletRequest getRequest();


    /**
     * Gets the response that was used to initialize this AsyncContext
     * by calling {@link ServletRequest#startAsync()} or
     * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)}.
     *
     * @return the response that was used to initialize this AsyncContext
     */
    public ServletResponse getResponse();


    /**
     * Checks if this AsyncContext was initialized with the original or
     * application-wrapped request and response objects.
     * 
     * <p>This information may be used by filters invoked in the
     * <i>outbound</i> direction, after a request was put into
     * asynchronous mode, to determine whether any request and/or response
     * wrappers that they added during their <i>inbound</i> invocation need
     * to be preserved for the duration of the asynchronous operation, or may
     * be released.
     *
     * @return true if this AsyncContext was initialized with the original
     * request and response objects by calling
     * {@link ServletRequest#startAsync()}, or if it was initialized by
     * calling
     * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)},
     * and neither the ServletRequest nor ServletResponse arguments 
     * carried any application-provided wrappers; false otherwise
     */
    public boolean hasOriginalRequestAndResponse();


    /**
     * Dispatches the request and response objects of this AsyncContext
     * to the servlet container.
     * 
     * <p>If the asynchronous cycle was started with
     * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)},
     * and the request passed is an instance of HttpServletRequest,
     * then the dispatch is to the URI returned by
     * {@link javax.servlet.http.HttpServletRequest#getRequestURI}.
     * Otherwise, the dispatch is to the URI of the request when it was
     * last dispatched by the container.
     *
     * <p>The following sequence illustrates how this will work:
     * <code><pre>
     * // REQUEST dispatch to /url/A
     * AsyncContext ac = request.startAsync();
     * ...
     * ac.dispatch(); // ASYNC dispatch to /url/A
     * 
     * // FORWARD dispatch to /url/B
     * getRequestDispatcher("/url/B").forward(request,response);
     * // Start async operation from within the target of the FORWARD
     * // dispatch
     * ac = request.startAsync();
     * ...
     * ac.dispatch(); // ASYNC dispatch to /url/A
     * 
     * // FORWARD dispatch to /url/B
     * getRequestDispatcher("/url/B").forward(request,response);
     * // Start async operation from within the target of the FORWARD
     * // dispatch
     * ac = request.startAsync(request,response);
     * ...
     * ac.dispatch(); // ASYNC dispatch to /url/B
     * </pre></code>
     *
     * <p>This method returns immediately after passing the request
     * and response objects to a container managed thread, on which the
     * dispatch operation will be performed.
     * If this method is called before the container-initiated dispatch
     * that called <tt>startAsync</tt> has returned to the container, the
     * dispatch operation will be delayed until after the container-initiated
     * dispatch has returned to the container.
     *
     * <p>The dispatcher type of the request is set to
     * <tt>DispatcherType.ASYNC</tt>. Unlike
     * {@link RequestDispatcher#forward(ServletRequest, ServletResponse)
     * forward dispatches}, the response buffer and
     * headers will not be reset, and it is legal to dispatch even if the
     * response has already been committed.
     *
     * <p>Control over the request and response is delegated
     * to the dispatch target, and the response will be closed when the
     * dispatch target has completed execution, unless
     * {@link ServletRequest#startAsync()} or
     * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)}
     * are called.
     * 
     * <p>Any errors or exceptions that may occur during the execution
     * of this method must be caught and handled by the container, as
     * follows:
     * <ol>
     * <li>Invoke, at their {@link AsyncListener#onError onError} method, all
     * {@link AsyncListener} instances registered with the ServletRequest
     * for which this AsyncContext was created, and make the caught 
     * <tt>Throwable</tt> available via {@link AsyncEvent#getThrowable}.</li>
     * <li>If none of the listeners called {@link #complete} or any of the
     * {@link #dispatch} methods, perform an error dispatch with a status code
     * equal to <tt>HttpServletResponse.SC_INTERNAL_SERVER_ERROR</tt>, and
     * make the above <tt>Throwable</tt> available as the value of the
     * <tt>RequestDispatcher.ERROR_EXCEPTION</tt> request attribute.</li>
     * <li>If no matching error page was found, or the error page did not call
     * {@link #complete} or any of the {@link #dispatch} methods, call
     * {@link #complete}.</li>
     * </ol>
     *
     * <p>There can be at most one asynchronous dispatch operation per
     * asynchronous cycle, which is started by a call to one of the
     * {@link ServletRequest#startAsync} methods. Any attempt to perform an
     * additional asynchronous dispatch operation within the same
     * asynchronous cycle will result in an IllegalStateException.
     * If startAsync is subsequently called on the dispatched request,
     * then any of the dispatch or {@link #complete} methods may be called.
     *
     * @throws IllegalStateException if one of the dispatch methods
     * has been called and the startAsync method has not been
     * called during the resulting dispatch, or if {@link #complete}
     * was called
     *
     * @see ServletRequest#getDispatcherType
     */
    public void dispatch();


    /**
     * Dispatches the request and response objects of this AsyncContext
     * to the given <tt>path</tt>.
     *
     * <p>The <tt>path</tt> parameter is interpreted in the same way 
     * as in {@link ServletRequest#getRequestDispatcher(String)}, within
     * the scope of the {@link ServletContext} from which this
     * AsyncContext was initialized.
     *
     * <p>All path related query methods of the request must reflect the
     * dispatch target, while the original request URI, context path,
     * path info, servlet path, and query string may be recovered from
     * the {@link #ASYNC_REQUEST_URI}, {@link #ASYNC_CONTEXT_PATH},
     * {@link #ASYNC_PATH_INFO}, {@link #ASYNC_SERVLET_PATH}, and
     * {@link #ASYNC_QUERY_STRING} attributes of the request. These
     * attributes will always reflect the original path elements, even under
     * repeated dispatches.
     *
     * <p>There can be at most one asynchronous dispatch operation per
     * asynchronous cycle, which is started by a call to one of the
     * {@link ServletRequest#startAsync} methods. Any attempt to perform an
     * additional asynchronous dispatch operation within the same
     * asynchronous cycle will result in an IllegalStateException.
     * If startAsync is subsequently called on the dispatched request,
     * then any of the dispatch or {@link #complete} methods may be called.
     *
     * <p>See {@link #dispatch()} for additional details, including error
     * handling.
     *
     * @param path the path of the dispatch target, scoped to the
     * ServletContext from which this AsyncContext was initialized
     *
     * @throws IllegalStateException if one of the dispatch methods
     * has been called and the startAsync method has not been
     * called during the resulting dispatch, or if {@link #complete}
     * was called
     *
     * @see ServletRequest#getDispatcherType
     */
    public void dispatch(String path);


    /**
     * Dispatches the request and response objects of this AsyncContext
     * to the given <tt>path</tt> scoped to the given <tt>context</tt>.
     *
     * <p>The <tt>path</tt> parameter is interpreted in the same way 
     * as in {@link ServletRequest#getRequestDispatcher(String)}, except that
     * it is scoped to the given <tt>context</tt>.
     *
     * <p>All path related query methods of the request must reflect the
     * dispatch target, while the original request URI, context path,
     * path info, servlet path, and query string may be recovered from
     * the {@link #ASYNC_REQUEST_URI}, {@link #ASYNC_CONTEXT_PATH},
     * {@link #ASYNC_PATH_INFO}, {@link #ASYNC_SERVLET_PATH}, and
     * {@link #ASYNC_QUERY_STRING} attributes of the request. These
     * attributes will always reflect the original path elements, even under
     * repeated dispatches.
     *
     * <p>There can be at most one asynchronous dispatch operation per
     * asynchronous cycle, which is started by a call to one of the
     * {@link ServletRequest#startAsync} methods. Any attempt to perform an
     * additional asynchronous dispatch operation within the same
     * asynchronous cycle will result in an IllegalStateException.
     * If startAsync is subsequently called on the dispatched request,
     * then any of the dispatch or {@link #complete} methods may be called.
     *
     * <p>See {@link #dispatch()} for additional details, including error
     * handling.
     *
     * @param context the ServletContext of the dispatch target
     * @param path the path of the dispatch target, scoped to the given
     * ServletContext
     *
     * @throws IllegalStateException if one of the dispatch methods
     * has been called and the startAsync method has not been
     * called during the resulting dispatch, or if {@link #complete}
     * was called
     *
     * @see ServletRequest#getDispatcherType
     */
    public void dispatch(ServletContext context, String path);


    /**
     * Completes the asynchronous operation that was started on the request
     * that was used to initialze this AsyncContext, closing the response
     * that was used to initialize this AsyncContext.
     *
     * <p>Any listeners of type {@link AsyncListener} that were registered
     * with the ServletRequest for which this AsyncContext was created will
     * be invoked at their {@link AsyncListener#onComplete(AsyncEvent)
     * onComplete} method.
     *
     * <p>It is legal to call this method any time after a call to
     * {@link ServletRequest#startAsync()} or
     * {@link ServletRequest#startAsync(ServletRequest, ServletResponse)},
     * and before a call to one of the <tt>dispatch</tt> methods
     * of this class. 
     * If this method is called before the container-initiated dispatch
     * that called <tt>startAsync</tt> has returned to the container, then
     * the call will not take effect (and any invocations of
     * {@link AsyncListener#onComplete(AsyncEvent)} will be delayed) until
     * after the container-initiated dispatch has returned to the container.
     */
    public void complete();


    /**
     * Causes the container to dispatch a thread, possibly from a managed
     * thread pool, to run the specified <tt>Runnable</tt>. The container may
     * propagate appropriate contextual information to the <tt>Runnable</tt>. 
     *
     * @param run the asynchronous handler
     */
    public void start(Runnable run);


    /**
     * Registers the given {@link AsyncListener} with the most recent
     * asynchronous cycle that was started by a call to one of the
     * {@link ServletRequest#startAsync} methods.
     *
     * <p>The given AsyncListener will receive an {@link AsyncEvent} when
     * the asynchronous cycle completes successfully, times out, or results
     * in an error.
     *
     * <p>AsyncListener instances will be notified in the order in which
     * they were added.
     *
     * @param listener the AsyncListener to be registered
     * 
     * @throws IllegalStateException if this method is called after
     * the container-initiated dispatch, during which one of the
     * {@link ServletRequest#startAsync} methods was called, has
     * returned to the container
     */
    public void addListener(AsyncListener listener);


    /**
     * Registers the given {@link AsyncListener} with the most recent
     * asynchronous cycle that was started by a call to one of the
     * {@link ServletRequest#startAsync} methods.
     *
     * <p>The given AsyncListener will receive an {@link AsyncEvent} when
     * the asynchronous cycle completes successfully, times out, or results
     * in an error.
     *
     * <p>AsyncListener instances will be notified in the order in which
     * they were added.
     *
     * <p>The given ServletRequest and ServletResponse objects will
     * be made available to the given AsyncListener via the
     * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
     * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse} methods,
     * respectively, of the {@link AsyncEvent} delivered to it. These objects
     * should not be read from or written to, respectively, at the time the
     * AsyncEvent is delivered, because additional wrapping may have
     * occurred since the given AsyncListener was registered, but may be used
     * in order to release any resources associated with them.
     *
     * @param listener the AsyncListener to be registered
     * @param servletRequest the ServletRequest that will be included
     * in the AsyncEvent
     * @param servletResponse the ServletResponse that will be included
     * in the AsyncEvent
     *
     * @throws IllegalStateException if this method is called after
     * the container-initiated dispatch, during which one of the
     * {@link ServletRequest#startAsync} methods was called, has
     * returned to the container
     */
    public void addListener(AsyncListener listener,
                            ServletRequest servletRequest,
                            ServletResponse servletResponse);


    /**
     * Instantiates the given {@link AsyncListener} class.
     *
     * <p>The returned AsyncListener instance may be further customized
     * before it is registered with this AsyncContext via a call to one of 
     * the <code>addListener</code> methods.
     *
     * <p>The given AsyncListener class must define a zero argument
     * constructor, which is used to instantiate it.
     *
     * <p>This method supports resource injection if the given
     * <tt>clazz</tt> represents a Managed Bean.
     * See the Java EE platform and JSR 299 specifications for additional
     * details about Managed Beans and resource injection.

     * <p>This method supports any annotations applicable to AsyncListener.
     *
     * @param clazz the AsyncListener class to instantiate
     *
     * @return the new AsyncListener instance
     *
     * @throws ServletException if the given <tt>clazz</tt> fails to be
     * instantiated
     */
    public <T extends AsyncListener> T createListener(Class<T> clazz)
        throws ServletException; 


    /**
     * Sets the timeout (in milliseconds) for this AsyncContext.
     *
     * <p>The timeout applies to this AsyncContext once the
     * container-initiated dispatch during which one of the
     * {@link ServletRequest#startAsync} methods was called has
     * returned to the container. 
     *
     * <p>The timeout will expire if neither the {@link #complete} method
     * nor any of the dispatch methods are called. A timeout value of
     * zero or less indicates no timeout. 
     * 
     * <p>If {@link #setTimeout} is not called, then the container's
     * default timeout, which is available via a call to
     * {@link #getTimeout}, will apply.
     *
     * @param timeout the timeout in milliseconds
     *
     * @throws IllegalStateException if this method is called after
     * the container-initiated dispatch, during which one of the
     * {@link ServletRequest#startAsync} methods was called, has
     * returned to the container
     */
    public void setTimeout(long timeout);


    /**
     * Gets the timeout (in milliseconds) for this AsyncContext.
     *
     * <p>This method returns the container's default timeout for
     * asynchronous operations, or the timeout value passed to the most
     * recent invocation of {@link #setTimeout}.
     *
     * <p>A timeout value of zero or less indicates no timeout.
     *
     * @return the timeout in milliseconds
     */
    public long getTimeout();

}
 比较简单,通过看代码中的注释就能知其大意了,异步就是不等待结果立即返回,这里start一般用新线程的方式,线程运行完后(#complete),需要通知(#addListener),以及超时检测处理(#setTimeout,由容器Tomcat来设置)

AsyncListener监听器

package javax.servlet;

import java.io.IOException;
import java.util.EventListener;

/**
 * Listener that will be notified in the event that an asynchronous
 * operation initiated on a ServletRequest to which the listener had been 
 * added has completed, timed out, or resulted in an error.
 *
 * @since Servlet 3.0
 */
public interface AsyncListener extends EventListener {
    
    /**
     * Notifies this AsyncListener that an asynchronous operation
     * has been completed.
     * 
     * <p>The {@link AsyncContext} corresponding to the asynchronous
     * operation that has been completed may be obtained by calling
     * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
     * <tt>event</tt>.
     *
     * <p>In addition, if this AsyncListener had been registered via a call
     * to {@link AsyncContext#addListener(AsyncListener,
     * ServletRequest, ServletResponse)}, the supplied ServletRequest and
     * ServletResponse objects may be retrieved by calling
     * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
     * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
     * respectively, on the given <tt>event</tt>.
     *
     * @param event the AsyncEvent indicating that an asynchronous
     * operation has been completed
     *
     * @throws IOException if an I/O related error has occurred during the
     * processing of the given AsyncEvent
     */
    public void onComplete(AsyncEvent event) throws IOException;


    /**
     * Notifies this AsyncListener that an asynchronous operation
     * has timed out.
     * 
     * <p>The {@link AsyncContext} corresponding to the asynchronous
     * operation that has timed out may be obtained by calling
     * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
     * <tt>event</tt>.
     *
     * <p>In addition, if this AsyncListener had been registered via a call
     * to {@link AsyncContext#addListener(AsyncListener,
     * ServletRequest, ServletResponse)}, the supplied ServletRequest and
     * ServletResponse objects may be retrieved by calling
     * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
     * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
     * respectively, on the given <tt>event</tt>.
     *
     * @param event the AsyncEvent indicating that an asynchronous
     * operation has timed out
     *
     * @throws IOException if an I/O related error has occurred during the
     * processing of the given AsyncEvent
     */
    public void onTimeout(AsyncEvent event) throws IOException;


    /**
     * Notifies this AsyncListener that an asynchronous operation 
     * has failed to complete.
     * 
     * <p>The {@link AsyncContext} corresponding to the asynchronous
     * operation that failed to complete may be obtained by calling
     * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
     * <tt>event</tt>.
     * 
     * <p>In addition, if this AsyncListener had been registered via a call
     * to {@link AsyncContext#addListener(AsyncListener,
     * ServletRequest, ServletResponse)}, the supplied ServletRequest and
     * ServletResponse objects may be retrieved by calling
     * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
     * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
     * respectively, on the given <tt>event</tt>.
     *
     * @param event the AsyncEvent indicating that an asynchronous
     * operation has failed to complete
     *
     * @throws IOException if an I/O related error has occurred during the
     * processing of the given AsyncEvent
     */
    public void onError(AsyncEvent event) throws IOException;


    /**
     * Notifies this AsyncListener that a new asynchronous cycle is being
     * initiated via a call to one of the {@link ServletRequest#startAsync}
     * methods.
     *
     * <p>The {@link AsyncContext} corresponding to the asynchronous
     * operation that is being reinitialized may be obtained by calling
     * {@link AsyncEvent#getAsyncContext getAsyncContext} on the given
     * <tt>event</tt>.
     * 
     * <p>In addition, if this AsyncListener had been registered via a call
     * to {@link AsyncContext#addListener(AsyncListener,
     * ServletRequest, ServletResponse)}, the supplied ServletRequest and
     * ServletResponse objects may be retrieved by calling
     * {@link AsyncEvent#getSuppliedRequest getSuppliedRequest} and
     * {@link AsyncEvent#getSuppliedResponse getSuppliedResponse},
     * respectively, on the given <tt>event</tt>.
     *
     * <p>This AsyncListener will not receive any events related to the
     * new asynchronous cycle unless it registers itself (via a call
     * to {@link AsyncContext#addListener}) with the AsyncContext that
     * is delivered as part of the given AsyncEvent.
     *
     * @param event the AsyncEvent indicating that a new asynchronous
     * cycle is being initiated
     *
     * @throws IOException if an I/O related error has occurred during the
     * processing of the given AsyncEvent
     */
    public void onStartAsync(AsyncEvent event) throws IOException;     

}

​ 监听器主要有4个事件,开始、完成、超时、错误。

SpringMVC中的相关组件

​ SpringMVC中异步请求相关组件 AsyncWebRequest、WebAsyncManager、WebAsyncUtils

AsyncWebRequest

image-20181220195645682

其实现有两个,其中NoSupportAsyncWebRequest不支持异步,所以我们只需要关注StandarServletAsyncWebRequest即可。(可以看到其实主要是对Servelt中支持异步的类的一些特性进行整合)

image-20181220200306736

具体细节看StandarServletAsyncWebRequest的实现

WebAsyncManager

package org.springframework.web.context.request.async;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.async.DeferredResult.DeferredResultHandler;
import org.springframework.web.util.UrlPathHelper;

/**
 * The central class for managing asynchronous request processing, mainly intended
 * as an SPI and not typically used directly by application classes.
 *
 * <p>An async scenario starts with request processing as usual in a thread (T1).
 * Concurrent request handling can be initiated by calling
 * {@link #startCallableProcessing(Callable, Object...) startCallableProcessing} or
 * {@link #startDeferredResultProcessing(DeferredResult, Object...) startDeferredResultProcessing},
 * both of which produce a result in a separate thread (T2). The result is saved
 * and the request dispatched to the container, to resume processing with the saved
 * result in a third thread (T3). Within the dispatched thread (T3), the saved
 * result can be accessed via {@link #getConcurrentResult()} or its presence
 * detected via {@link #hasConcurrentResult()}.
 *
 * @author Rossen Stoyanchev
 * @since 3.2
 *
 * @see org.springframework.web.context.request.AsyncWebRequestInterceptor
 * @see org.springframework.web.servlet.AsyncHandlerInterceptor
 * @see org.springframework.web.filter.OncePerRequestFilter#shouldNotFilterAsyncDispatch
 * @see org.springframework.web.filter.OncePerRequestFilter#isAsyncDispatch
 */
public final class WebAsyncManager {

   private static final Object RESULT_NONE = new Object();

   private static final Log logger = LogFactory.getLog(WebAsyncManager.class);

   private static final UrlPathHelper urlPathHelper = new UrlPathHelper();

   private static final CallableProcessingInterceptor timeoutCallableInterceptor =
         new TimeoutCallableProcessingInterceptor();

   private static final DeferredResultProcessingInterceptor timeoutDeferredResultInterceptor =
         new TimeoutDeferredResultProcessingInterceptor();


   private AsyncWebRequest asyncWebRequest;

   private AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(this.getClass().getSimpleName());

   private Object concurrentResult = RESULT_NONE;

   private Object[] concurrentResultContext;

   private final Map<Object, CallableProcessingInterceptor> callableInterceptors =
         new LinkedHashMap<Object, CallableProcessingInterceptor>();

   private final Map<Object, DeferredResultProcessingInterceptor> deferredResultInterceptors =
         new LinkedHashMap<Object, DeferredResultProcessingInterceptor>();


   /**
    * Package private constructor.
    * @see WebAsyncUtils#getAsyncManager(javax.servlet.ServletRequest)
    * @see WebAsyncUtils#getAsyncManager(org.springframework.web.context.request.WebRequest)
    */
   WebAsyncManager() {
   }

   /**
    * Configure the {@link AsyncWebRequest} to use. This property may be set
    * more than once during a single request to accurately reflect the current
    * state of the request (e.g. following a forward, request/response
    * wrapping, etc). However, it should not be set while concurrent handling
    * is in progress, i.e. while {@link #isConcurrentHandlingStarted()} is
    * {@code true}.
    *
    * @param asyncWebRequest the web request to use
    */
   public void setAsyncWebRequest(final AsyncWebRequest asyncWebRequest) {
      Assert.notNull(asyncWebRequest, "AsyncWebRequest must not be null");
      Assert.state(!isConcurrentHandlingStarted(), "Can't set AsyncWebRequest with concurrent handling in progress");
      this.asyncWebRequest = asyncWebRequest;
      this.asyncWebRequest.addCompletionHandler(new Runnable() {
         @Override
         public void run() {
            asyncWebRequest.removeAttribute(WebAsyncUtils.WEB_ASYNC_MANAGER_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
         }
      });
   }

   /**
    * Configure an AsyncTaskExecutor for use with concurrent processing via
    * {@link #startCallableProcessing(Callable, Object...)}.
    * <p>By default a {@link SimpleAsyncTaskExecutor} instance is used.
    */
   public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
      this.taskExecutor = taskExecutor;
   }

   /**
    * Whether the selected handler for the current request chose to handle the
    * request asynchronously. A return value of "true" indicates concurrent
    * handling is under way and the response will remain open. A return value
    * of "false" means concurrent handling was either not started or possibly
    * that it has completed and the request was dispatched for further
    * processing of the concurrent result.
    */
   public boolean isConcurrentHandlingStarted() {
      return ((this.asyncWebRequest != null) && this.asyncWebRequest.isAsyncStarted());
   }

   /**
    * Whether a result value exists as a result of concurrent handling.
    */
   public boolean hasConcurrentResult() {
      return (this.concurrentResult != RESULT_NONE);
   }

   /**
    * Provides access to the result from concurrent handling.
    *
    * @return an Object, possibly an {@code Exception} or {@code Throwable} if
    * concurrent handling raised one.
    * @see #clearConcurrentResult()
    */
   public Object getConcurrentResult() {
      return this.concurrentResult;
   }

   /**
    * Provides access to additional processing context saved at the start of
    * concurrent handling.
    *
    * @see #clearConcurrentResult()
    */
   public Object[] getConcurrentResultContext() {
      return this.concurrentResultContext;
   }

   /**
    * Get the {@link CallableProcessingInterceptor} registered under the given key.
    * @param key the key
    * @return the interceptor registered under that key or {@code null}
    */
   public CallableProcessingInterceptor getCallableInterceptor(Object key) {
      return this.callableInterceptors.get(key);
   }

   /**
    * Get the {@link DeferredResultProcessingInterceptor} registered under the given key.
    * @param key the key
    * @return the interceptor registered under that key or {@code null}
    */
   public DeferredResultProcessingInterceptor getDeferredResultInterceptor(Object key) {
      return this.deferredResultInterceptors.get(key);
   }

   /**
    * Register a {@link CallableProcessingInterceptor} under the given key.
    * @param key the key
    * @param interceptor the interceptor to register
    */
   public void registerCallableInterceptor(Object key, CallableProcessingInterceptor interceptor) {
      Assert.notNull(key, "Key is required");
      Assert.notNull(interceptor, "CallableProcessingInterceptor  is required");
      this.callableInterceptors.put(key, interceptor);
   }

   /**
    * Register a {@link CallableProcessingInterceptor} without a key.
    * The key is derived from the class name and hashcode.
    * @param interceptors one or more interceptors to register
    */
   public void registerCallableInterceptors(CallableProcessingInterceptor... interceptors) {
      Assert.notNull(interceptors, "A CallableProcessingInterceptor is required");
      for (CallableProcessingInterceptor interceptor : interceptors) {
         String key = interceptor.getClass().getName() + ":" + interceptor.hashCode();
         this.callableInterceptors.put(key, interceptor);
      }
   }

   /**
    * Register a {@link DeferredResultProcessingInterceptor} under the given key.
    * @param key the key
    * @param interceptor the interceptor to register
    */
   public void registerDeferredResultInterceptor(Object key, DeferredResultProcessingInterceptor interceptor) {
      Assert.notNull(key, "Key is required");
      Assert.notNull(interceptor, "DeferredResultProcessingInterceptor is required");
      this.deferredResultInterceptors.put(key, interceptor);
   }

   /**
    * Register a {@link DeferredResultProcessingInterceptor} without a key.
    * The key is derived from the class name and hashcode.
    * @param interceptors one or more interceptors to register
    */
   public void registerDeferredResultInterceptors(DeferredResultProcessingInterceptor... interceptors) {
      Assert.notNull(interceptors, "A DeferredResultProcessingInterceptor is required");
      for (DeferredResultProcessingInterceptor interceptor : interceptors) {
         String key = interceptors.getClass().getName() + ":" + interceptors.hashCode();
         this.deferredResultInterceptors.put(key, interceptor);
      }
   }

   /**
    * Clear {@linkplain #getConcurrentResult() concurrentResult} and
    * {@linkplain #getConcurrentResultContext() concurrentResultContext}.
    */
   public void clearConcurrentResult() {
      this.concurrentResult = RESULT_NONE;
      this.concurrentResultContext = null;
   }

   /**
    * Start concurrent request processing and execute the given task with an
    * {@link #setTaskExecutor(AsyncTaskExecutor) AsyncTaskExecutor}. The result
    * from the task execution is saved and the request dispatched in order to
    * resume processing of that result. If the task raises an Exception then
    * the saved result will be the raised Exception.
    *
    * @param callable a unit of work to be executed asynchronously
    * @param processingContext additional context to save that can be accessed
    * via {@link #getConcurrentResultContext()}
    * @throws Exception If concurrent processing failed to start
    *
    * @see #getConcurrentResult()
    * @see #getConcurrentResultContext()
    */
   @SuppressWarnings({"unchecked", "rawtypes" })
   public void startCallableProcessing(final Callable<?> callable, Object... processingContext) throws Exception {
      Assert.notNull(callable, "Callable must not be null");
      startCallableProcessing(new WebAsyncTask(callable), processingContext);
   }

   /**
    * Use the given {@link WebAsyncTask} to configure the task executor as well as
    * the timeout value of the {@code AsyncWebRequest} before delegating to
    * {@link #startCallableProcessing(Callable, Object...)}.
    *
    * @param webAsyncTask a WebAsyncTask containing the target {@code Callable}
    * @param processingContext additional context to save that can be accessed
    * via {@link #getConcurrentResultContext()}
    * @throws Exception If concurrent processing failed to start
    */
   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");

      Long timeout = webAsyncTask.getTimeout();
      if (timeout != null) {
         this.asyncWebRequest.setTimeout(timeout);
      }

      AsyncTaskExecutor executor = webAsyncTask.getExecutor();
      if (executor != null) {
         this.taskExecutor = executor;
      }

      List<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>();
      interceptors.add(webAsyncTask.getInterceptor());
      interceptors.addAll(this.callableInterceptors.values());
      interceptors.add(timeoutCallableInterceptor);

      final Callable<?> callable = webAsyncTask.getCallable();
      final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors);

      this.asyncWebRequest.addTimeoutHandler(new Runnable() {
         @Override
         public void run() {
            logger.debug("Processing timeout");
            Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable);
            if (result != CallableProcessingInterceptor.RESULT_NONE) {
               setConcurrentResultAndDispatch(result);
            }
         }
      });

      this.asyncWebRequest.addCompletionHandler(new Runnable() {
         @Override
         public void run() {
            interceptorChain.triggerAfterCompletion(asyncWebRequest, callable);
         }
      });

      interceptorChain.applyBeforeConcurrentHandling(asyncWebRequest, callable);

      startAsyncProcessing(processingContext);

      this.taskExecutor.submit(new Runnable() {
         @Override
         public void run() {
            Object result = null;
            try {
               interceptorChain.applyPreProcess(asyncWebRequest, callable);
               result = callable.call();
            }
            catch (Throwable t) {
               result = t;
            }
            finally {
               result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
            }
            setConcurrentResultAndDispatch(result);
         }
      });
   }

   private void setConcurrentResultAndDispatch(Object result) {
      synchronized (WebAsyncManager.this) {
         if (hasConcurrentResult()) {
            return;
         }
         concurrentResult = result;
      }

      if (asyncWebRequest.isAsyncComplete()) {
         logger.error("Could not complete async processing due to timeout or network error");
         return;
      }

      logger.debug("Concurrent result value [" + concurrentResult + "]");
      logger.debug("Dispatching request to resume processing");

      asyncWebRequest.dispatch();
   }

   /**
    * Start concurrent request processing and initialize the given
    * {@link DeferredResult} with a {@link DeferredResultHandler} that saves
    * the result and dispatches the request to resume processing of that
    * result. The {@code AsyncWebRequest} is also updated with a completion
    * handler that expires the {@code DeferredResult} and a timeout handler
    * assuming the {@code DeferredResult} has a default timeout result.
    *
    * @param deferredResult the DeferredResult instance to initialize
    * @param processingContext additional context to save that can be accessed
    * via {@link #getConcurrentResultContext()}
    * @throws Exception If concurrent processing failed to start
    *
    * @see #getConcurrentResult()
    * @see #getConcurrentResultContext()
    */
   public void startDeferredResultProcessing(
         final DeferredResult<?> deferredResult, Object... processingContext) throws Exception {

      Assert.notNull(deferredResult, "DeferredResult must not be null");
      Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");

      Long timeout = deferredResult.getTimeoutValue();
      if (timeout != null) {
         this.asyncWebRequest.setTimeout(timeout);
      }

      List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>();
      interceptors.add(deferredResult.getInterceptor());
      interceptors.addAll(this.deferredResultInterceptors.values());
      interceptors.add(timeoutDeferredResultInterceptor);

      final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors);

      this.asyncWebRequest.addTimeoutHandler(new Runnable() {
         @Override
         public void run() {
            try {
               interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult);
            }
            catch (Throwable t) {
               setConcurrentResultAndDispatch(t);
            }
         }
      });

      this.asyncWebRequest.addCompletionHandler(new Runnable() {
         @Override
         public void run() {
            interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult);
         }
      });

      interceptorChain.applyBeforeConcurrentHandling(asyncWebRequest, deferredResult);

      startAsyncProcessing(processingContext);

      try {
         interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
         deferredResult.setResultHandler(new DeferredResultHandler() {
            @Override
            public void handleResult(Object result) {
               result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
               setConcurrentResultAndDispatch(result);
            }
         });
      }
      catch (Throwable t) {
         setConcurrentResultAndDispatch(t);
      }
   }

   private void startAsyncProcessing(Object[] processingContext) {

      clearConcurrentResult();
      this.concurrentResultContext = processingContext;

      this.asyncWebRequest.startAsync();

      if (logger.isDebugEnabled()) {
         HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
         String requestUri = urlPathHelper.getRequestUri(request);
         logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
      }
   }

}

​ 类中有两个重要的方法#startCallableProcessing(用于处理Callable和WebAsyncTask类型),#startDeferredResultProcessing(用于处理DeferredResult和ListenableFuture类型),是启动异步处理的入口方法,它们一共做了三件事

  • 启动异步处理
  • 给Request设置相应属性(timeout、timeoutHandler和completionHandler)
  • 在相应的位置调用相应的拦截器(CallableProcessingInterceptor和DeferredResultProcessingInterceptor都封装在相应的Chain中)

更多细节 可查看startCallableProcessing的执行过程

WebAsyncUtils

package org.springframework.web.context.request.async;

import java.lang.reflect.Constructor;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.beans.BeanUtils;
import org.springframework.util.ClassUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.WebRequest;

/**
 * Utility methods related to processing asynchronous web requests.
 *
 * @author Rossen Stoyanchev
 * @since 3.2
 */
public abstract class WebAsyncUtils {

   public static final String WEB_ASYNC_MANAGER_ATTRIBUTE = WebAsyncManager.class.getName() + ".WEB_ASYNC_MANAGER";

   private static Constructor<?> standardAsyncRequestConstructor;


   /**
    * Obtain the {@link WebAsyncManager} for the current request, or if not
    * found, create and associate it with the request.
    */
   public static WebAsyncManager getAsyncManager(ServletRequest servletRequest) {
      WebAsyncManager asyncManager = (WebAsyncManager) servletRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE);
      if (asyncManager == null) {
         asyncManager = new WebAsyncManager();
         servletRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager);
      }
      return asyncManager;
   }

   /**
    * Obtain the {@link WebAsyncManager} for the current request, or if not
    * found, create and associate it with the request.
    */
   public static WebAsyncManager getAsyncManager(WebRequest webRequest) {
      int scope = RequestAttributes.SCOPE_REQUEST;
      WebAsyncManager asyncManager = (WebAsyncManager) webRequest.getAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, scope);
      if (asyncManager == null) {
         asyncManager = new WebAsyncManager();
         webRequest.setAttribute(WEB_ASYNC_MANAGER_ATTRIBUTE, asyncManager, scope);
      }
      return asyncManager;
   }

   /**
    * Create an AsyncWebRequest instance. By default an instance of
    * {@link StandardServletAsyncWebRequest} is created if running in Servlet
    * 3.0 (or higher) environment or as a fallback, an instance of
    * {@link NoSupportAsyncWebRequest} is returned.
    *
    * @param request the current request
    * @param response the current response
    * @return an AsyncWebRequest instance, never {@code null}
    */
   public static AsyncWebRequest createAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
      return ClassUtils.hasMethod(ServletRequest.class, "startAsync") ?
            createStandardServletAsyncWebRequest(request, response) : new NoSupportAsyncWebRequest(request, response);
   }

   private static AsyncWebRequest createStandardServletAsyncWebRequest(HttpServletRequest request, HttpServletResponse response) {
      try {
         if (standardAsyncRequestConstructor == null) {
            String className = "org.springframework.web.context.request.async.StandardServletAsyncWebRequest";
            Class<?> clazz = ClassUtils.forName(className, WebAsyncUtils.class.getClassLoader());
            standardAsyncRequestConstructor = clazz.getConstructor(HttpServletRequest.class, HttpServletResponse.class);
         }
         return (AsyncWebRequest) BeanUtils.instantiateClass(standardAsyncRequestConstructor, request, response);
      }
      catch (Throwable t) {
         throw new IllegalStateException("Failed to instantiate StandardServletAsyncWebRequest", t);
      }
   }

}

​ 可以看到这个类主要是提供WebAsyncManage、AsyncWebRequest 相关的操作

SpringMVC中请求的支持

  1. FrameworkServlet中添加了RequestBindingInterceptor

  2. RequestMappingHandlerAdapter#invokeHandlerMethod 提供了对异步请求的核心支持

    ​ 2.1 创建AsyncWebRequest并设置超时时间

    ​ 2.2 对当前请求WebAsyncManager设置了属性(taskExecutor、asyncWebRequest、callabltinterceptors和deferredResultInterceptors)

    ​ 2.3 并判断是否有结果,如果有对结果进行处理。requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);

    ​ 2.4 然后执行方法 requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

  3. 返回值处理器,AsyncTaskMethodReturnValueHandler CallableMethodReturnValueHandler DeferredResultMethodReturnValueHandler ListenableFutureReturnValueHandler

  4. DispatcherServlet#doDispatcher,如果是异步直接返回。

/**
 * Invoke the {@link RequestMapping} handler method preparing a {@link ModelAndView}
 * if view resolution is required.
 */
private ModelAndView invokeHandleMethod(HttpServletRequest request,
      HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

   ServletWebRequest webRequest = new ServletWebRequest(request, response);

   WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
   ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
   ServletInvocableHandlerMethod requestMappingMethod = createRequestMappingMethod(handlerMethod, binderFactory);

   ModelAndViewContainer mavContainer = new ModelAndViewContainer();
   mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
   modelFactory.initModel(webRequest, mavContainer, requestMappingMethod);
   mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

   AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
   asyncWebRequest.setTimeout(this.asyncRequestTimeout);

   final 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();

      if (logger.isDebugEnabled()) {
         logger.debug("Found concurrent result value [" + result + "]");
      }
      requestMappingMethod = requestMappingMethod.wrapConcurrentResult(result);
   }

   requestMappingMethod.invokeAndHandle(webRequest, mavContainer);

   if (asyncManager.isConcurrentHandlingStarted()) {
      return null;
   }

   return getModelAndView(mavContainer, modelFactory, webRequest);
}

spring webFlux

等待填坑


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:servlet_spring-mvc

字数:11.6k

本文作者:zhengyumin

发布时间:2019-02-03, 15:50:16

最后更新:2020-01-25, 16:27:29

原始链接:http://zyumin.github.io/2019/02/03/spring-mvc/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。