前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >springboot之mvc原理(一)-请求处理

springboot之mvc原理(一)-请求处理

作者头像
叔牙
发布2020-11-19 17:10:38
2.6K0
发布2020-11-19 17:10:38
举报

Springboot之mvc原理(一)-请求处理

篇幅较大,认真看我可能需要10分钟!

概述

springboot出现以后,我们搭建spring应用的复杂度大大降低,仅仅需要简单的注解和若干配置类就能构建简单的应用,这些都依赖于springboot默认集成了一整套的spring核心组件,比如在新版本的springboot的中,web和aop能力是完全不用配置和注解开始就能直接使用,这也是springboot设计和存在的初衷,尽可能大的程度上降低spring应用搭建和配置成本,将研发人员的主要精力尽可能投入在业务开发中。

前边讲到,springboot目前已经对web提供了原生支持,那么对springmvc的一整套流程也是无缝接入,本篇文章我们将着重讲述springboot mvc对请求处理的原理实现和源码分析。

原理&源码分析

1

请求处理流程

上图是spring mvc的请求处理流程,将请求处理分成了8个核心步骤,如果细分可能会有更多步骤,这里我们先简单分析一下每个步骤做的事情,细节的话后边源码分析会讲到:

  • 接收请求:servlet容器(tomcat和jetty等)接收到来自浏览器的请求,调用应用的Servlet的service方法,最终会调到DispatcherServlet的doService方法
  • 寻找处理器:DispatcherServlet会从维护的HandlerMapping列表中寻找合适的handler,基于注解的应用会使用RequestMappingHandlerMapping
  • 调用处理器:调用被@RequestMapping注解后包装成的HttpMethod
  • 调用模型处理业务:调用@RequestMapping方法的具体实现执行相关业务逻辑
  • 获取处理结果:将上一步的处理结果封装成ModelAndView返回给DispatcherServlet
  • 视图映射处理:调用ViewResolver返回View(JSONView,PDFView等)
  • 数据传给View:将数据渲染成不同的数据模型
  • 结果响应:将结果放入response并响应给浏览器

2

原理解析

我们以tomcat容器为例,springboot应用启动后,浏览器发送请求会先经过tomcat,然后tomcat的执行引擎会寻找应用的Servlet实现并调用其service方法,但是如果我们通过xml配置文件或者配置类显式配置了DispatcherServlet并配置了init方法执行时机,那么应用启动后会先执行init方法进行初始化,如果没有显式配置DispatcherServlet,DispatcherServletConfiguration中默认暴露的DispatcherServlet是没有设置init方法执行时机的,那么应用启动后并不会执行其init方法,而是在应用第一次接收到请求的时候先执行init方法,再执行service方法,并且保证init只执行一次。

3

源码分析

springboot处理请求的核心类是DispatcherServlet,我们先看一下其类继承关系:

核心的继承关系是:

DispatcherServlet最终实现了Java定义的Servlet接口(和Jdbc一个套路,jdk官方提供接口规范,各大厂商自己实现),Servlet中比较核心的两个方法是init和service:

代码语言:javascript
复制
/**
 * Called by the servlet container to indicate to a servlet that the servlet
 * is being placed into service.
 *
 * <p>
 * The servlet container calls the <code>init</code> method exactly once
 * after instantiating the servlet. The <code>init</code> method must
 * complete successfully before the servlet can receive any requests.
 *
 * <p>
 * The servlet container cannot place the servlet into service if the
 * <code>init</code> method
 * <ol>
 * <li>Throws a <code>ServletException</code>
 * <li>Does not return within a time period defined by the Web server
 * </ol>
 *
 *
 * @param config
 *            a <code>ServletConfig</code> object containing the servlet's
 *            configuration and initialization parameters
 *
 * @exception ServletException
 *                if an exception has occurred that interferes with the
 *                servlet's normal operation
 *
 * @see UnavailableException
 * @see #getServletConfig
 */
public void init(ServletConfig config) throws ServletException;
/**
 * Called by the servlet container to allow the servlet to respond to a
 * request.
 *
 * <p>
 * This method is only called after the servlet's <code>init()</code> method
 * has completed successfully.
 *
 * <p>
 * The status code of the response always should be set for a servlet that
 * throws or sends an error.
 *
 *
 * <p>
 * Servlets typically run inside multithreaded servlet containers that can
 * handle multiple requests concurrently. Developers must be aware to
 * synchronize access to any shared resources such as files, network
 * connections, and as well as the servlet's class and instance variables.
 * More information on multithreaded programming in Java is available in <a
 * href
 * ="http://java.sun.com/Series/Tutorial/java/threads/multithreaded.html">
 * the Java tutorial on multi-threaded programming</a>.
 *
 *
 * @param req
 *            the <code>ServletRequest</code> object that contains the
 *            client's request
 *
 * @param res
 *            the <code>ServletResponse</code> object that contains the
 *            servlet's response
 *
 * @exception ServletException
 *                if an exception occurs that interferes with the servlet's
 *                normal operation
 *
 * @exception IOException
 *                if an input or output exception occurs
 */
public void service(ServletRequest req, ServletResponse res)
        throws ServletException, IOException;

从方法签名以及注释来看,init和service的作用和执行时机如下:

  • init:由servlet容器调用,表示该servlet已经准备好提供服务了,并且容器在实例化servlet之后只会在提供服务前调用一次init方法
  • service:在接收到http(s)请求后,由servlet容器调用并处理请求,返回相应的处理结果和响应码

我们前边有说过,servlet的service方法最终会调用到DispatcherServlet的doService方法,但是在此之前会执行init方法,会对DispatcherServlet做一些初始化准备工作,我们分别看一下两个核心方法的执行时序图:

从时序图中可以看出,init方法调用最终会为DispatcherServlet初始化准备9大组件供其使用:

  • initMultipartResolver:初始化文件处理组件
  • initLocaleResolver:初始化国际化组件,没有配置默认使用AcceptHeaderLocaleResolver
  • initThemeResolver:初始化主题组件,基本很少用
  • initHandlerMappings:初始化HandlerMapping组件,也就是请求与HttpMethod的映射关系
  • initHandlerAdapters:初始化HandlerAdapter组件,请求的真正处理由该组件负责
  • initHandlerExceptionResolvers:初始化异常处理组件,负责处理异常
  • initRequestToViewNameTranslator:请求视图转换组件,从request中解析出具体的ViewName
  • initViewResolvers:初始化视图解析器,负责将数据解析并渲染成具体的视图模型
  • initFlashMapManager:初始化快速映射组件,主要负责跳转和重定向相关,在restful服务模式下基本废弃

servlet容器接收到请求后调用应用的servlet实现,最终会调用到DispatcherServlet的doDispatch方法,对于前边的准备工作不是本篇分析的主要内容,我们重点分析DispatcherServlet的doDispatch方法,看一下其核心实现:

代码语言:javascript
复制
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 {
            //检查是否是文件上传请求,如果是转换成MultipartHttpServletRequest
      processedRequest = checkMultipart(request);
      multipartRequestParsed = (processedRequest != request);

      // Determine handler for the current request.
            //获取Handler处理链
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
      }

      // Determine handler adapter for the current request.
            //确定当前请求的处理程序适配器
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

      // Process last-modified header, if supported by the handler.
      String method = request.getMethod();
      boolean isGet = "GET".equals(method);
      if (isGet || "HEAD".equals(method)) {
        long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
        if (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;
      }

      // Actually invoke the handler.
            //实际调用处理程序
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

      applyDefaultViewName(processedRequest, mv);
            //执行后置拦截器逻辑
      mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
      dispatchException = ex;
    }
    catch (Throwable err) {
      // As of 4.3, we're processing Errors thrown from handler methods as well,
      // making them available for @ExceptionHandler methods and other scenarios.
      dispatchException = new NestedServletException("Handler dispatch failed", err);
    }
        //处理执行结果
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  }
  catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
        new NestedServletException("Handler processing failed", err));
  }
  finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
      // Instead of postHandle and afterCompletion
      if (mappedHandler != null) {
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
      }
    }
    else {
      // Clean up any resources used by a multipart request.
      if (multipartRequestParsed) {
        cleanupMultipart(processedRequest);
      }
    }
  }
}

代码里边比较核心并且我们本篇需要关注的点已经加了简单的注释,分别是:

  • 获取Handler处理链:根据初始化的HandlerMapping返回对应处理链
  • 获取处理程序适配器:根据初始化的HandlerAdapter是否支持当前Handler返回相应适配器
  • 执行前置拦截逻辑:执行系统配置和用户自定义的HandlerInterceptor前置逻辑preHandle
  • 实际调用处理程序:调用Handler适配器执行具体业务逻辑
  • 执行后置拦截逻辑:执行系统配置和用户自定义的HandlerInterceptor后置逻辑postHandle
  • 处理执行结果:对Handler适配器执行具体业务逻辑的返回数据进行处理

getHandler返回请求处理链,看一下实现:

代码语言:javascript
复制
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  if (this.handlerMappings != null) {
    for (HandlerMapping hm : this.handlerMappings) {
      if (logger.isTraceEnabled()) {
        logger.trace(
            "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
      }
      HandlerExecutionChain handler = hm.getHandler(request);
      if (handler != null) {
        return handler;
      }
    }
  }
  return null;
}

先获取配置的HandlerMapping列表,springboot应用启动后会有7个HandlerMapping,分别是SimpleUrlHandlerMapping,BeanNameUrlHandlerMapping和RequestMappingHandlerMapping等,第一个是基于xml配置的,第二个需要自定义controller继承AbstractController或者实现Controller接口,第三个是支持基于配置的HandlerMapping,前两个由于在springboot轻量级简洁化开发模式主导情况下基本处于废弃或者濒临报废状态,在免配置注解驱动的springboot应用中RequestMappingHandlerMapping大发神威。代码中遍历配置的HandlerMapping列表,如果HandlerMapping能够返回Handler处理链则直接返回,否则继续遍历或者最终返回空,继续看HandlerMapping的getHandler实现:

代码语言:javascript
复制
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  Object handler = getHandlerInternal(request);
  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);
  }

  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  if (CorsUtils.isCorsRequest(request)) {
    CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
    CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  }
  return executionChain;
}

该方法先根据请求(url)获取Handler(不再展开),如果没有获取到返回空(比如非法url),如果存在把匹配的拦截器(系统配置和自定义)添加到处理链中并返回。

我们接着看getHandlerAdapter实现:

代码语言:javascript
复制
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  Object handler = getHandlerInternal(request);
  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);
  }

  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  if (CorsUtils.isCorsRequest(request)) {
    CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
    CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  }
  return executionChain;
}

获取系统配置的HandlerAdapter列表,分别是RequestMappingHandlerAdapter、HttpRequestHandlerAdapter和SimpleControllerHandlerAdapter,支持HttpMethod、HttpRequestHandler和Controller类型的Handler,前边讲到springboot应用使用基于注解支持的RequestMappingHandlerMapping,会将@RequestMapping注解的方法包装成HttpMethod,所以此处会返回RequestMappingHandlerAdapter。

继续看mappedHandler.applyPreHandle方法:

代码语言:javascript
复制
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
    for (int i = 0; i < interceptors.length; i++) {
      HandlerInterceptor interceptor = interceptors[i];
      if (!interceptor.preHandle(request, response, this.handler)) {
        triggerAfterCompletion(request, response, null);
        return false;
      }
      this.interceptorIndex = i;
    }
  }
  return true;
}

该方法会获取所以符合条件的系统和自定义拦截器,并按照优先级执行其preHandle方法,如果结果返回false直接返回false给DispatcherServlet结束调用,比如我们对于权限拦截的注解就在此处执行。

接下来执行HandlerAdapter的handle方法就开始处理我们应用中编写的具体的业务逻辑,看一下代码实现:

代码语言:javascript
复制
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
    throws Exception {

  return handleInternal(request, response, (HandlerMethod) handler);
}

调用抽象类AbstractHandlerMethodAdapter的handle方法,接着调用子类RequestMappingHandlerMapping的handleInternal方法:

代码语言:javascript
复制
protected ModelAndView handleInternal(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

  ModelAndView mav;
  checkRequest(request);
  // Execute invokeHandlerMethod in synchronized block if required.
  if (this.synchronizeOnSession) {
    HttpSession session = request.getSession(false);
    if (session != null) {
      Object mutex = WebUtils.getSessionMutex(session);
      synchronized (mutex) {
        mav = invokeHandlerMethod(request, response, handlerMethod);
      }
    }
    else {
      // No HttpSession available -> no mutex necessary
      mav = invokeHandlerMethod(request, response, handlerMethod);
    }
  }
  else {
    // No synchronization on session demanded at all...
    mav = invokeHandlerMethod(request, response, handlerMethod);
  }

  if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
    if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
      applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
    }
    else {
      prepareResponse(response);
    }
  }
  return mav;
}

该方法调用内部方法invokeHandlerMethod并返回视图:

代码语言:javascript
复制
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
    HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

  ServletWebRequest webRequest = new ServletWebRequest(request, response);
  try {
    WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
    ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

    ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
    if (this.argumentResolvers != null) {
      invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
      invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }
    invocableMethod.setDataBinderFactory(binderFactory);
    invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

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

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

    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    asyncManager.setTaskExecutor(this.taskExecutor);
    asyncManager.setAsyncWebRequest(asyncWebRequest);
    asyncManager.registerCallableInterceptors(this.callableInterceptors);
    asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

    if (asyncManager.hasConcurrentResult()) {
      Object result = asyncManager.getConcurrentResult();
      mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
      asyncManager.clearConcurrentResult();
      if (logger.isDebugEnabled()) {
        logger.debug("Found concurrent result value [" + result + "]");
      }
      invocableMethod = invocableMethod.wrapConcurrentResult(result);
    }

    invocableMethod.invokeAndHandle(webRequest, mavContainer);
    if (asyncManager.isConcurrentHandlingStarted()) {
      return null;
    }

    return getModelAndView(mavContainer, modelFactory, webRequest);
  }
  finally {
    webRequest.requestCompleted();
  }
}

从代码中可以看出,先根据HandlerMethod创建一个可执行的ServletInvocableHandlerMethod,然后调用invokeAndHandle执行逻辑,最后组装并返回视图模型:

接续回到DispatcherServlet主流程,接着看mappedHandler.applyPostHandle方法:

代码语言:javascript
复制
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
    throws Exception {
  HandlerInterceptor[] interceptors = getInterceptors();
  if (!ObjectUtils.isEmpty(interceptors)) {
    for (int i = interceptors.length - 1; i >= 0; i--) {
      HandlerInterceptor interceptor = interceptors[i];
      interceptor.postHandle(request, response, this.handler, mv);
    }
  }
}

同前置拦截处理一样,先获取符合条件的拦截器,按照优先级倒序执行postHandle逻辑,和前置拦截器执行顺序刚好顺序相反:

最后,processDispatchResult处理返回结果并返回相应的数据视图:

代码语言:javascript
复制
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
    @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
    @Nullable Exception exception) throws Exception {

  boolean errorView = false;
  if (exception != null) {
    if (exception instanceof ModelAndViewDefiningException) {
      logger.debug("ModelAndViewDefiningException encountered", exception);
      mv = ((ModelAndViewDefiningException) exception).getModelAndView();
    }
    else {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
    }
  }
  // Did the handler return a view to render?
  if (mv != null && !mv.wasCleared()) {
    render(mv, request, response);
    if (errorView) {
      WebUtils.clearErrorRequestAttributes(request);
    }
  }
  else {
    if (logger.isDebugEnabled()) {
      logger.debug("Null ModelAndView returned to DispatcherServlet with name '" + getServletName() +
          "': assuming HandlerAdapter completed request handling");
    }
  }
  if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    // Concurrent handling started during a forward
    return;
  }
  if (mappedHandler != null) {
    mappedHandler.triggerAfterCompletion(request, response, null);
  }
}

具体的数据视图解析与返回,本篇不再做赘述,之前一篇文章《spring内容协商》有详细描述,借用一张时序图:

也就是CNVR作为一个代理视图解析器,将请求委托给具体的视图解析器处理并返回结果。

总结

篇幅有点大,并且分析的内容也比较多,但是认真分析并不是很难,简单总结一下,本篇重点讲述了springboot应用对http请求的处理过程以及从源码维度分析了DispatcherServlet的工作原理,但是这些都是在servlet容器启动之后的请求处理分析,请思考一个问题,为什么springboot应用启动后就能支持web能力,在启动过程中帮我们做了什么?后续系列将继续分析springboot对webmvc的自动配置和原生支持。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 PersistentCoder 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档