SpringMVC请求处理

SpringMVC 请求处理流程

SpringMVC运行流程图.jpg

1. DispatcherServlet 九大组件

//DispatcherServlet中的九大组件、全是接口,我们完全可以自定义实现。Spring默认也都准备好了这些组件的实现
/** MultipartResolver used by this servlet. */ //https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-servlet-special-bean-types
@Nullable  //文件上传解析器
private MultipartResolver multipartResolver;

/** LocaleResolver used by this servlet. */
@Nullable  //国际化解析器 Locale(区域信息)
private LocaleResolver localeResolver;

/** ThemeResolver used by this servlet. */
@Nullable //主题解析器
private ThemeResolver themeResolver;

/** List of HandlerMappings used by this servlet. */
@Nullable //Handler(处理器、能处理请求的人(Controller))的映射:【保存的就是所有请求都由谁来处理的映射关系】
private List<HandlerMapping> handlerMappings;

/** List of HandlerAdapters used by this servlet. */
@Nullable //Handler(处理器、能处理请求的人(Controller))的适配器;【超级反射工具】
private List<HandlerAdapter> handlerAdapters;

/** List of HandlerExceptionResolvers used by this servlet. */
@Nullable //Handler的异常解析器,异常处理功能
private List<HandlerExceptionResolver> handlerExceptionResolvers;

/** RequestToViewNameTranslator used by this servlet. */
@Nullable //把请求转成视图名(我们要跳转的页面地址)的翻译器【没啥用】
private RequestToViewNameTranslator viewNameTranslator;

/** FlashMapManager used by this servlet. */
@Nullable //闪存管理器
private FlashMapManager flashMapManager;

/** List of ViewResolvers used by this servlet. */
@Nullable //视图解析器(我们去哪些页面,怎么过去?)
private List<ViewResolver> viewResolvers;

2. DispatcherServlet 初始化

/**
 * This implementation calls {@link #initStrategies}.
 */
@Override //
protected void onRefresh(ApplicationContext context) {
   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); //容器中有就用,没有就是null 解析请求
   initLocaleResolver(context); //从容器中获取,没有用默认 国际化
   initThemeResolver(context); //从容器中获取,没有用默认 主题
   initHandlerMappings(context); //从容器中获取,没有用默认 (处理器、能处理请求的人(Controller))的映射:【保存的就是所有请求都由谁来处理的映射关系】
   initHandlerAdapters(context); //从容器中获取,没有用默认 Handler(处理器、能处理请求的人(Controller))的适配器;【超级反射工具】
   initHandlerExceptionResolvers(context); //从容器中获取,没有用默认  Handler的异常解析器,异常处理功能
   initRequestToViewNameTranslator(context); //Spring没有浓重说他,//从容器中获取,没有用默认 初始化视图转发
   initViewResolvers(context); //从容器中获取,没有用默认 初始化视图解析器,将ModelAndView保存的视图信息,转换为一个视图,输出数据
   initFlashMapManager(context); //从容器中获取,没有用默认 闪存管理器
}

我们可以在这里打断点,发现进入这里的方法是FrameworkServlet->initWebApplicationContext->configureAndRefreshWebApplicationContext(cwac); //配置并且刷新容器 通过监听事件回调到onRefresh。

2.1 加载默认策略

private void initLocaleResolver(ApplicationContext context) {
   try { //容器中先来获取
      this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
      if (logger.isTraceEnabled()) {
         logger.trace("Detected " + this.localeResolver);
      }
      else if (logger.isDebugEnabled()) {
         logger.debug("Detected " + this.localeResolver.getClass().getSimpleName());
      }
   }
   catch (NoSuchBeanDefinitionException ex) {
      // We need to use the default. 容器中没有,读取默认配置文件进行加载
      this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); //获取默认策略
      if (logger.isTraceEnabled()) {
         logger.trace("No LocaleResolver '" + LOCALE_RESOLVER_BEAN_NAME +
               "': using default [" + this.localeResolver.getClass().getSimpleName() + "]");
      }
   }
}

这边获取默认配置文件策略可以看下

3. DispatcherServlet doService

@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
   logRequest(request);

   // Keep a snapshot of the request attributes in case of an include,
   // to be able to restore the original attributes after the include.
   Map<String, Object> attributesSnapshot = null; //把request域的所有属性提前保存
   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域中方便处理 Make framework objects available to handlers and view objects.
   request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
   request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver); //国际化解析器
   request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver); //主题解析器
   request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());

   if (this.flashMapManager != null) { //闪存管理器(重定向携带数据)
      FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
      if (inputFlashMap != null) {
         request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
      }
      request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
      request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
   }

   RequestPath previousRequestPath = null;
   if (this.parseRequestPath) {
      previousRequestPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
      ServletRequestPathUtils.parseAndCache(request);
   }

   try {
      doDispatch(request, response); //【核心】处理派发功能
   }
   finally {
      if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
         // Restore the original attribute snapshot, in case of an include.
         if (attributesSnapshot != null) {
            restoreAttributesAfterInclude(request, attributesSnapshot);
         }
      }
      ServletRequestPathUtils.setParsedRequestPath(previousRequestPath, request);
   }
}

3.1【核心】处理派发功能

//SpringMVC处理请求的核心流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null; //handler(目标方法)的执行链
   boolean multipartRequestParsed = false; //文件上传标志
   //对异步请求的支持(Servlet3.0以后才有的,Webflux)
   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
         processedRequest = checkMultipart(request); //检查当前是否文件上传请求
         multipartRequestParsed = (processedRequest != request);

         //构造出了【目标方法+拦截器整个链路】决定使用哪个Handler处理当前请求 Determine handler for the current request.
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {  //如果找不到人处理,就send 404
            noHandlerFound(processedRequest, response);
            return;
         }

         //适配器怎么找的、 Determine handler adapter for the current request.
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }
         //所有拦截器的 preHandle 执行
         if (!mappedHandler.applyPreHandle(processedRequest, response)) {
            return; //使用 mappedHandler整个链
         }

         //真正来执行目标方法 Actually invoke the handler.(反射执行目标方法、确定参数值,处理返回值【封装成ModelAndView】)
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv); //所有拦截器的postHandle
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      } //处理结果
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {  //下面的即使执行完,异常还是抛出去
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}
3.1.1 mappedHandler = getHandler(processedRequest); 构造出了【目标方法+拦截器整个链路】决定使用哪个Handler处理当前请求
@Nullable //所有 策略模式 ,尝试每一个策略。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   if (this.handlerMappings != null) { //处理器的映射;
      for (HandlerMapping mapping : this.handlerMappings) {
         HandlerExecutionChain handler = mapping.getHandler(request);
         if (handler != null) {
            return handler;
         }
      }
   }
   return null;
}

HandlerMapping 在我们注册的九大组件的时候初始化进来的。

进去看看这个方法 HandlerExecutionChain handler = mapping.getHandler(request);

@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
   Object handler = getHandlerInternal(request); //HandlerMapping的registry中找映射,返回HandlerMethod,真正执行当前请求的方法
   if (handler == null) {
      handler = getDefaultHandler();
   }
   if (handler == null) {
      return null;
   }
   // Bean name or resolved handler?
   if (handler instanceof String) {
      String handlerName = (String) handler;
      handler = obtainApplicationContext().getBean(handlerName);
   }

   // Ensure presence of cached lookupPath for interceptors and others
   if (!ServletRequestPathUtils.hasCachedPath(request)) {
      initLookupPath(request);
   }
   //找到前面的目标方法以后,还要构造一个处理器链;
   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 (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
      CorsConfiguration config = getCorsConfiguration(handler, request);
      if (getCorsConfigurationSource() != null) {
         CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
         config = (globalConfig != null ? globalConfig.combine(config) : config);
      }
      if (config != null) {
         config.validateAllowCredentials();
      }
      executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
   }

   return executionChain;
}
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
   String lookupPath = initLookupPath(request);
   this.mappingRegistry.acquireReadLock();
   try {
      HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); //寻找当前请求谁能处理
      return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
   }
   finally {
      this.mappingRegistry.releaseReadLock();
   }
}

就是在mappingRegistry 里面查找谁能够处理当前的请求。那么mappingRegistry什么时候注册进去的?

注册mappingRegistry
//分析所有的Controller;里面的每一个@RequestMapping 注解才能知道这个事情
public void registerMapping(T mapping, Object handler, Method method) { //哪个请求由哪个方法处理会通过这个进行注册
   if (logger.isTraceEnabled()) {
      logger.trace("Register \"" + mapping + "\" to " + method.toGenericString());
   }
   this.mappingRegistry.register(mapping, handler, method);
}

AbstractHandlerMethodMapping

public void register(T mapping, Object handler, Method method) {
   this.readWriteLock.writeLock().lock();
   try {
      HandlerMethod handlerMethod = createHandlerMethod(handler, method);
      validateMethodMapping(handlerMethod, mapping);

      Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
      for (String path : directPaths) {
         this.pathLookup.add(path, mapping);
      }

      String name = null;
      if (getNamingStrategy() != null) {
         name = getNamingStrategy().getName(handlerMethod, mapping);
         addMappingName(name, handlerMethod);
      }

      CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
      if (corsConfig != null) {
         corsConfig.validateAllowCredentials();
         this.corsLookup.put(handlerMethod, corsConfig);
      }

      this.registry.put(mapping,
            new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
   }
   finally {
      this.readWriteLock.writeLock().unlock();
   }
}

我们可以在这里打个断点看看

我们发现AbstractHandlerMethodMapping 是继承InitializingBean。我们之前挖坑的。后置增强会调用afterPropertiesSet方法,这个可以自己去跟一下

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
      @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
      @Nullable Exception exception) throws Exception {

   boolean errorView = false;
   //如果有异常处理异常,以下if内全是异常处理环节
   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?   为啥?@ResponseBody(提前在解析返回值的时候,就已经把数据写出去了,所以这一步就没有了)
   if (mv != null && !mv.wasCleared()) {
      render(mv, request, response); //渲染ModeAndView,来解析模型和视图;最终决定响应效果
      if (errorView) {
         WebUtils.clearErrorRequestAttributes(request);
      }
   }
   else {
      if (logger.isTraceEnabled()) {
         logger.trace("No view rendering, null ModelAndView returned.");
      }
   }

   if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
      // Concurrent handling started during a forward
      return;
   }

   if (mappedHandler != null) {
      // Exception (if any) is already handled..
      mappedHandler.triggerAfterCompletion(request, response, null);
   }
}
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
   // Determine locale for request and apply it to the response.
   Locale locale =
         (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
   response.setLocale(locale);
   //默认AcceptHeaderLocaleResolver会根据请求头中的Accept-Language字段决定浏览器能接受哪种中文/英文 页面
   View view; //视图
   String viewName = mv.getViewName(); //适配器执行完目标方法以后返回的ModelAndView对象里面有  index.jsp
   if (viewName != null) {
      //************ 把目标方法的返回值字符串index.jsp 真正转为能用的View对象; We need to resolve the view name.
      view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
      if (view == null) {
         throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
               "' in servlet with name '" + getServletName() + "'");
      }
   }
   else {
      // No need to lookup: the ModelAndView object contains the actual View object.
      view = mv.getView();
      if (view == null) {
         throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
               "View object in servlet with name '" + getServletName() + "'");
      }
   }

   // Delegate to the View object for rendering.
   if (logger.isTraceEnabled()) {
      logger.trace("Rendering view [" + view + "] ");
   }
   try {
      if (mv.getStatus() != null) {
         response.setStatus(mv.getStatus().value());
      }
      view.render(mv.getModelInternal(), request, response);
   }
   catch (Exception ex) {
      if (logger.isDebugEnabled()) {
         logger.debug("Error rendering view [" + view + "]", ex);
      }
      throw ex;
   }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容