SpringMvc请求处理流程与源码探秘

本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

原文链接:blog.ouyangsihai.cn >> SpringMvc请求处理流程与源码探秘

流程梳理

dispatcherServlet作为前端控制器的主要作用就是接受请求与处理响应。

不过它不是传统意义上的servlet,它在接受到请求后采用转发的方式,将具体工作交给专业人士去做。

参与角色主要有:

  • 前端控制器(DispatcherServlet)
  • 处理映射器(HandlerMapping)
  • 处理适配器(HandlerAdapter)
  • 处理器((Handler)Controller)
  • 视图解析器(ViewReslover)
  • 视图(View)
  • 处理映射器(HandlerMapping)

    处理器((Handler)Controller)

    视图(View)

    SpringMvc请求处理流程与源码探秘

    (找了一张图,把请求过程与步骤清晰的呈现了出来)

     

    第一步:前端控制器dispatcher接受请求

  • Client---url---Dispatcher
  • 第二步:前端控制器去发起handler映射查找请求

  • Dispatcher---HttpServletRequest--- HandlerMapping
  • 第三步:处理器映射器查找hanlder并返回HandlerExetuionChain

  • Dispatcher ---HandlerExeutionChain---HandlerMapping
  • 第四步:前端控制器发起请求处理器适配器请求执行

  • Dispatcher---Handler--- HandlerAdapter
  • 第五步:处理器适配器去调用handler执行

  • HandlerAdapter---HttpServletRequest Handler(Controller)
  • 第六步:处理器处理后返回ModelAndView给HandlerAdapter

  • HandlerAdapter ---ModelAndView---Handler(Controller)
  • 第七步:处理器适配器将ModelAndView返回给前端控制器

  • Dispatcher ---ModelAndView---HandlerAdapter
  • 第八步:前端控制器请求视图解析器解析ModelAndView

  • Dispatcher---ModelAndView--- ViewReslover
  • 第九步:视图解析器解析视图后返回视图View给前端控制器

  • Dispatcher ---View---ViewReslover
  • 第十步:前端控制器请求视图要求渲染视图

  • Dispatcher---View---render
  • 第十一步:前端控制器返回响应

  • Response ---Dispatcher
  • 源码探秘

    第一步接受请求:

    我们可以来看看DispatcherServlet的继承结构

    SpringMvc请求处理流程与源码探秘

    其实DispatcherServlet能处理请求是因为HttpServlet类的service方法,而HttpServlet又来自Servlet接口定义的规范。

    SpringMvc请求处理流程与源码探秘

    可以看到抽象类HttpServlet实现了接口Servlet的service方法,根据请求类型不同执行了不同的方法(doGet,doPost)

    SpringMvc请求处理流程与源码探秘

    当请进来后,由HttpServlet的子类FrameworkServlet重写的service方法执行请求,可以看到437行子类调用了父类的service方法,然后在父类执行doGet之类的方法时,由于子类FrameworkServlet重写了父类方法,交由子类执行,所以进到了我的doGet断点里面,它调用了处理请求方法。

    接下来我们看看ProcessRequest方法的源码

    
     1 protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     2        long startTime = System.currentTimeMillis();
     3        Throwable failureCause = null;
     4        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
     5        LocaleContext localeContext = this.buildLocaleContext(request);
     6        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
     7        ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
     8        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
     9        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor(null));
    10        this.initContextHolders(request, localeContext, requestAttributes);
    11
    12        try {
    13            this.doService(request, response);
    14        } catch (IOException | ServletException var16) {
    15            failureCause = var16;
    16            throw var16;
    17        } catch (Throwable var17) {
    18            failureCause = var17;
    19            throw new NestedServletException("Request processing failed", var17);
    20        } finally {
    21            this.resetContextHolders(request, previousLocaleContext, previousAttributes);
    22            if (requestAttributes != null) {
    23                requestAttributes.requestCompleted();
    24            }
    25
    26            this.logResult(request, response, (Throwable)failureCause, asyncManager);
    27            this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
    28        }
    29
    30    }
    

    前面一系列初始化工作我们先不管,看看重要的部分,try里面的doService方法

    SpringMvc请求处理流程与源码探秘

    跟踪进去看了一下,由于它是抽象方法,所以会由子类实现和执行,也就是我们的DispatchServlet类了

    SpringMvc请求处理流程与源码探秘
    
     1protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
     2        this.logRequest(request);
     3        MapString, Object attributesSnapshot = null;
     4        if (WebUtils.isIncludeRequest(request)) {
     5            attributesSnapshot = new HashMap();
     6            Enumeration attrNames = request.getAttributeNames();
     7
     8            label95:
     9            while(true) {
    10                String attrName;
    11                do {
    12                    if (!attrNames.hasMoreElements()) {
    13                        break label95;
    14                    }
    15
    16                    attrName = (String)attrNames.nextElement();
    17                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));
    18
    19                attributesSnapshot.put(attrName, request.getAttribute(attrName));
    20            }
    21        }
    22
    23        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
    24        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
    25        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
    26        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
    27        if (this.flashMapManager != null) {
    28            FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
    29            if (inputFlashMap != null) {
    30                request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
    31            }
    32
    33            request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
    34            request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);
    35        }
    36
    37        try {
    38            this.doDispatch(request, response);
    39        } finally {
    40            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
    41                this.restoreAttributesAfterInclude(request, attributesSnapshot);
    42            }
    43
    44        }
    45
    46    }
    

    所以第一步也就完成了,第一步的任务就是走进这里来。

    第二步:前端控制器去发起handler映射查找请求

    Dispatcher—HttpServletRequest— HandlerMapping

    上面的源码中主要工作就是给request实例设置一系列参数,要注意的就是doDispatch方法,这里面就是mvc的核心了,前面第一张交互图里面的流程都是在这里实现的。

    SpringMvc请求处理流程与源码探秘

    可以看到,通过HttpRequestServlet作为参数请求handlerMapping

    第三步:处理器映射器查找hanlder并返回HandlerExetuionChain

    Dispatcher —HandlerExeutionChain—HandlerMapping

    可以看到上图中返回了mappedHandler变量,就是HandlerExtuceChain类型

    SpringMvc请求处理流程与源码探秘

    可以看到,已经找到并返回了我们的HomeController处理器(Hanlder)

    第四步:前端控制器发起请求处理器适配器请求执行

     Dispatcher—Handler— HandlerAdapter

    SpringMvc请求处理流程与源码探秘

    从508行可以看到,适配器传入handler对象和reaquest等信息,执行handler()方法

    第五步:处理器适配器去调用handler执行

    HandlerAdapter—HttpServletRequest Handler(Controller)

    SpringMvc请求处理流程与源码探秘

    从这里可以看到,调用的handlerInternal是个抽象方法,会调用子类的实现方法,子类由RequestMappingHandlerAdapter实现,这个类也是我们经常在xml里面配置的类

    SpringMvc请求处理流程与源码探秘 SpringMvc请求处理流程与源码探秘

    方法执行后返回我们的index

    SpringMvc请求处理流程与源码探秘

    第六步:处理器处理后返回ModelAndView给HandlerAdapter

    HandlerAdapter —ModelAndView—Handler(Controller)

    SpringMvc请求处理流程与源码探秘

    第七步:处理器适配器将ModelAndView返回给前端控制器

    Dispatcher —ModelAndView—HandlerAdapter

    SpringMvc请求处理流程与源码探秘

    第八步:前端控制器请求视图解析器解析ModelAndView

    Dispatcher—ModelAndView— ViewReslover

    SpringMvc请求处理流程与源码探秘

    第九步:视图解析器解析视图后返回视图View给前端控制器

    Dispatcher —View—ViewReslover

    SpringMvc请求处理流程与源码探秘

    可以看到,返回的视图,url指向index.jsp页面

    第十步:前端控制器请求视图要求渲染视图

    Dispatcher—View—render

    如果View对象不为空,将会调用render方法渲染

    SpringMvc请求处理流程与源码探秘

    如果返回的是json对象,属于接口的,是不会走这里的

    SpringMvc请求处理流程与源码探秘

    此时会找对应的视图解析器去渲染

    SpringMvc请求处理流程与源码探秘

    里面其实也没干啥,就做了个跳转,到jsp页面去绑定数据

    SpringMvc请求处理流程与源码探秘

    第十一步:前端控制器返回响应

    Response —Dispatcher

    SpringMvc请求处理流程与源码探秘

    到这里也就基本上完了。

    SpringMvc请求处理流程与源码探秘

    处理请求完成后做了个重置工作,然后发布一个事件,你可以选择监听这个事件,做相应处理。

    再看看response里面

    SpringMvc请求处理流程与源码探秘

    这个就是我们页面上的内容了。

    SpringMvc请求处理流程与源码探秘

    原文始发于微信公众号(JAVA烂猪皮):

    本人花费半年的时间总结的《Java面试指南》已拿腾讯等大厂offer,已开源在github ,欢迎star!

    本文GitHub https://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了6个月总结的一线大厂Java面试总结,本人已拿大厂offer,欢迎star

    原文链接:blog.ouyangsihai.cn >> SpringMvc请求处理流程与源码探秘


     上一篇
    面试官——你分析过SpringMVC的源码吗? 面试官——你分析过SpringMVC的源码吗?
    点击上方“Java知音”,选择“置顶公众号” 技术文章第一时间送达! 相关阅读 1. MVC使用 在研究源码之前,先来回顾以下springmvc 是如何配置的,这将能使我们更容易理解源码。 1.1 web.xml servlet
    2021-04-05
    下一篇 
    Spring Boot 集成Mybatis实现多数据源 Spring Boot 集成Mybatis实现多数据源
    作者:txxs https://blog.csdn.net/maoyeqiu/article https://blog.csdn.net/maoyeqiu/article 总体来说多数据源配置有两种方式,一种是静态
    2021-04-05