SpringMVC的工作原理及底层剖析,你值得一看

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
日志服务 SLS,月写入数据量 50GB 1个月
简介: 剩下的都在刚开始那段代码中了,其实这个也没啥就是简单的看看MVC工作的时候底层在干啥,不合适的地方多多指教。

今天分析的是MVC在工作的时候底层干了些啥,仅仅是工作的时后,就是从DispatcherServlet接受到一个请求开始

简单分析一下这个流程

9a64235a0cfd67775a9687bd2703a6b3_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQ1MDAxMDAy,size_16,color_FFFFFF,t_70#pic_center.png


1.DispatcherServle表示前端控制器,是整个springMVC的控制中心,用户发出请求,

1.1 DispatcherServle接受拦请求并拦截,

假设请求的url为:http://localhost:8080/SpringMVC/hello

如上url拆分为三部分

http://localhost:8080服务器域名

SpringMVC部署在服务器上的web站点

hello表示控制器

通过分析,如上url表示的意思为:请求位于服务器locahost:8080上的SpringMVC站点的Hello控制器

1.2 HandlerMapping为处理器映射,DispatcherServle自行调用HandlerMapping,HandlerMapping根据url查找Handler

1.3 HandlerExecution表示具体的handler,其主要作用就是根据url查找控制器,如上url被查找控制器为Hello

1.4 HandlerExecution将解析后的数据传递给DispatcherServle,如解析控制器映射等

1.5 HandlerAdapter表示处理器适配器,其按照特定的规则去执行Handler

1.6 Handler让具体的controller执行

1.7 controller将具体的执行信息返回HandlerAdapter,如Model和View

1.8 HandlerAdapter将试图逻辑命或模型传递给DispatcherServle

1.9 DispatcherServle调用试图解析器,ViewEsolver来解析HandlerAdapter传递的逻辑试图名

1.10 视图解析器将解析的逻辑视图名传给DispatcherServle

1.11 DispatcherServlet根据试图解析器解析的试图结果,掉欧阳那个具体的试图

1.12 呈现给用户


View是一个接口,他的实现类支持很多种类型

接下来看看底层代码,每一步具体都干了什么

首先在web.xml中找到如下代码

<servlet>
  <servlet-name>dispatcher</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
  <load-on-startup>1</load-on-startup>
</servlet>

然后按住ctrl,鼠标左键点击DispatcherServlet进到底层

进来之后不要迷茫,直接Ctrl+f搜索doDispatch这个方法,然后就就会看到如下代码


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 {          //检查请求是否是multipart(即文件上传),若是进行相关处理
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                          //通过handermapping映射获取HandlerExecutionChain(处理链中包括了interceptor的前置和后置方法)          // Determine handler for the current request.
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
          //根据处理器(handler及HandlerExecutionChain)获取处理器适配器(处理器适配器是为了提供统一接口进行后续处理,从而支持多种类型的处理器)
                // 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;
                    }
                }
          //执行chain中拦截器附加的预处理方法,即preHandle方法                  
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
          //适配器统一执行handle方法(适配器统一接口的作用),此处是真正处理业务逻辑的地方
                // Actually invoke the handler.
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);          //执行chain中拦截器附加的后处理方法,即postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }        //处理分发结果:包括解析视图并进行视图渲染,执行chain中拦截器附加的后处理方法,即afterCompletion方法。具体方法内容见下方源码。
            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);
                }
            }
        }
    }

把这段代码拆分一下,一步一步的来

第一步:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    **当前请求
      HttpServletRequest processedRequest = request;
      *HandlerMapping会把请求映射为HandlerExecutionChain类型的handler对象;
    处理器执行链=我们即将执行的controller+拦截器
      HandlerExecutionChain mappedHandler = null;
      *判断是否是文件上传请求
      *(MVC默认不支持文件格式)
      boolean multipartRequestParsed = false;
      *主要用来管理异步请求;什么时候用到异步请求呢?
      **业务逻辑复杂的时候,为了防止请求线程阻塞,需要委托给另一个线程的时候
      WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

扩展(什么是拦截器?)

Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理。例如通过拦截器可以进行权限验证、记录请求信息的日志、判断用户是否登录等。

要使用Spring MVC中的拦截器,就需要对拦截器类进行定义和配置。通常拦截器类可以通过两种方式来定义:

1.通过实现HandlerInterceptor接口,或继承HandlerInterceptor接口的实现类(如HandlerInterceptorAdapter)来定义。

2.通过实现WebRequestInterceptor接口,或继承WebRequestInterceptor接口的实现类来定义。


第二步:当收到请求时 checkMultipart() 方法开始执行,具体看看这个方法


1.首先说一下这个方法的作用:判断请求中是否包含文件,那么,如何判断呢?接下来一起看看这个方法具体做了啥
  protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
  *首先两个判断:判断这个文件是否为空;判断请求中是否包含文件*
  *.isMultipart(request):这个方法就是判断请求中是否包含文件*
        if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        *其次再获取到的request是否为空*
        *WebUtils:spring的工具类,主要用于Web应用程序,供各种框架使用。*
         *getNativeRequest():抽象了各种request,如果要获得实际真正的request,则调getNativeRequest()或者getNativeRequest(XXX.class)方法*
            if (WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class) != null) {
              *打印日志*
                this.logger.debug("Request is already a MultipartHttpServletRequest - if not in a forward, this typically results from an additional MultipartFilter in web.xml");
            } else {
              *判断request的一场是否和MultipartException相同,不是这个异常,正常返回解析数据,是这个异常就表示请中包含文件,打印日志,就直接把这个请求返回去了*
                if (!(request.getAttribute("javax.servlet.error.exception") instanceof MultipartException)) {
                  *resolveMultipart:对请求的数据进行解析,解析成MultipartFile*
                  *因为这个方法的返回值是HttpServletRequest,所以解析成 MultipartFile并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,返回*
                    return this.multipartResolver.resolveMultipart(request);
                }
                this.logger.debug("Multipart resolution failed for current request before - skipping re-resolution for undisturbed error rendering");
            }
        }
        return request;
    }
流程分析
MultipartResolver 用于处理文件上传,当收到请求时 DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中是否包含文件。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象中,最后传递给 Controller

3.1扩展(关于 this.logger.trace,this.logger.debug)

log4j定义了8个级别的log(除去OFF和ALL,可以说分为6个级别),优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。

1.ALL 最低等级的,用于打开所有日志记录。

2.TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用。

3.DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。

4.INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。

5.WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。

6.ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。

7.FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。

8.OFF 最高等级的,用于关闭所有日志记录。

如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。


第三步:根据请求路径寻找controller,具体怎么找呢?看接下来的getHandler(processedRequest)方法


> checkMultipart()检查完这个请求合格后把请求路径返回到DispatcherServlet
DispatcherServlet(分发作用),所以它知道路径检查完合格后,应该给HandlerMapping
接下来的方法就是DispatcherServlet把路径给到HandlerMapping后的操作
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        if (this.handlerMappings != null) {
          *迭代这个handlerMappings
            Iterator var2 = this.handlerMappings.iterator();
            while(var2.hasNext()) {
            HandlerMapping mapping = (HandlerMapping)var2.next();
            HandlerExecutionChain handler =mapping.getHandler(request);
                if (handler != null) {
                    return handler;
                }
            }
        }
        return null;
    }
这段代码的核心就是getHandler(request)这个方法,我们一起看看
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  //获取handler的具体逻辑,留给子类实现
  Object handler = getHandlerInternal(request);
  //null handler,采用默认的handler,即属性defaultHandler
  if (handler == null) {
    handler = getDefaultHandler();
  }
  //还是null handler,直接返回null
  if (handler == null) {
    return null;
  }
  //handler是beanName, 从容器里获取对应的bean
  // Bean name or resolved handler?
  if (handler instanceof String) {
    String handlerName = (String) handler;
    handler = obtainApplicationContext().getBean(handlerName);
  }
  //根据handler和request获取处理器链HandlerExecutionChain
  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
  //CORS请求的处理
  if (CorsUtils.isCorsRequest(request)) {
    //全局配置
    CorsConfiguration globalConfig = this.globalCorsConfigSource.getCorsConfiguration(request);
    //handler的单独配置
    CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
    //handler的所有配置,全局配置+单独配置
    CorsConfiguration config = (globalConfig != null ? globalConfig.combine(handlerConfig) : handlerConfig);
    //根据cors配置更新HandlerExecutionChain
    executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  }
  return executionChain;
}

logger.isTraceEnabled方法作用是判断记录器Trace跟踪是否激活。Trace跟踪激活后一般会打印比较详细的信息。

第四步:上一步可以说是获取到conreoller 的名字了,然后根据controller的名字找对应的controller,

具体看这个方法:

HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    for (HandlerAdapter ha : this.handlerAdapters) {
      *logger.isTraceEnabled方法作用是判断记录器Trace跟踪是否激活。Trace跟踪激活后一般会打印比较详细的信息。*
      if (logger.isTraceEnabled()) {
        logger.trace("Testing handler adapter [" + ha + "]");
      }
      *supports方法的主要作用在于判断当前的HandlerAdapter是否能够支持当前的handler的适配*
      if (ha.supports(handler)) {
      如果支持就返回这个HandlerAdapter(适配器)
        return ha;
      }
    }
    throw new ServletException("No adapter for handler [" + handler +"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  }

第五步:然后再来分析接下来这一段

getLastModified()关于这个方法我给大家找了一篇博客,大家可以看看

  // Process last-modified header, if supported by the handler.
        String method = request.getMethod();
        boolean isGet = "GET".equals(method);
        if (isGet || "HEAD".equals(method)) {
        ha表示一个适配器,适配器调用了一下这个getLastModified()方法,传了两个参数一个是mappedHandler.getHandler()方法返回来的handler(controller),一个是请求
          long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
          然后就是打印日志
          if (logger.isDebugEnabled()) {
            logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
          }
          判断getLastModified的返回值是不是文件,以及是不是get方法,不是文件,是Get方法的话就返回去
          if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
            return;
          }
        }
        applyPreHandle()方法在获取到所有拦截器后,通过for循环调用其中每个拦截器HandlerInterceptor的preHandle()方法,此过程可以类比对用户数据进行加密的过程
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }

剩下的都在刚开始那段代码中了,其实这个也没啥就是简单的看看MVC工作的时候底层在干啥,不合适的地方多多指教。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
6月前
|
设计模式 前端开发 Java
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringMVC的核心原理和运作机制(总体框架原理篇)
【深入浅出Spring原理及实战】「夯实基础系列」360全方位渗透和探究SpringMVC的核心原理和运作机制(总体框架原理篇)
69 0
|
缓存 Java 数据库
Spring框架(一) 底层核心原理解析
这个才是我们想要看的结果 ,我们可以简单分析一下 , userServiceBase的test1()方法也是有事务存在的 , 同时userServiceBase也是一个Bean , 它最终也会产生一个代理对象去当做一个Bean , 碎玉UserService而言 , 我要给userServiceBase这个属性去赋值 , 那么他肯定要从Spring容器中找到一个userServiceBase的一个Bean来赋值 , 所以他找到的就是Spring事务所产生的userServiceBase的代理对象 , 所以这个注解就是有用的
142 0
|
XML Java 数据格式
Spring框架(二) 底层架构核心概念解析-四万字你值得一看
上面说到解析为BeanDefintion之后会注册到Spring容器中 , 那么什么是容器? 其实在DefaultListableBeanFactory这个类中就有体现 , 源码中是这样定义的
91 0
|
1月前
|
XML 存储 前端开发
手动开发-实现SpringMVC底层机制--小试牛刀
手动开发-实现SpringMVC底层机制--小试牛刀
14 0
|
4月前
|
XML 监控 Java
Spring框架的核心原理与应用实践
Spring框架的核心原理与应用实践
|
6月前
|
前端开发 Java 数据库
MVC架构简述
MVC架构简述
57 4
|
前端开发 Java Maven
“深入探究SpringMVC的工作原理与入门实践“
“深入探究SpringMVC的工作原理与入门实践“
81 0
|
前端开发
前端学习笔记202305学习笔记第二十九天-什么是mvc-前后端的设计思想1
前端学习笔记202305学习笔记第二十九天-什么是mvc-前后端的设计思想1
37 0
前端学习笔记202305学习笔记第二十九天-什么是mvc-前后端的设计思想1
|
前端开发
前端学习笔记202305学习笔记第二十九天-什么是mvc-前后端的设计思想2
前端学习笔记202305学习笔记第二十九天-什么是mvc-前后端的设计思想2
44 0
|
前端开发 Java Spring
讲讲 SpringMVC 的优点
讲讲 SpringMVC 的优点
44 0