SpringMVC的执行流程与源码分析

发布时间:2024年01月17日

引言

通过深入分析Spring MVC的源码,我们可以更好地理解其工作原理和内部机制。这有助于我们更好地使用该框架进行Web应用程序的开发,并解决实际开发中遇到的问题。同时,对于学习和研究Spring MVC框架的人来说,阅读源码并进行分析也是一种重要的学习和提升手段。

SpringMVC概述

Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建?Web?应用程序的全功能 MVC 模块。是目前主流的实现MVC设计模式的框架,提供前端路由映射、视图解析等功能.

MVC模式

MVC是Model-View-Controller的缩写,是一种常用的软件设计模式。在这种设计模式中,模型层(Model)负责处理业务逻辑和数据,视图层(View)负责呈现用户界面,而控制器层(Controller)负责处理用户交互。

MVC把三层架构中的UI层再度进行了分化,分成了控制器、视图、实体三个部分,控制器完成页面逻辑,通过实体来与界面层完成通话;而Controller层直接与三层架构中的业务逻辑层进行对话。三层架构和MVC可以共存。 三层是基于业务逻辑来分的,而MVC是基于页面来分的。

特点:

 MVC重要特点就是两种分离:

  视图和数据模型的分离:使用不同的视图对相同的数据进行展示;分离可视和不可视的组件,能够对模型进行独立测试。因为分离了可视组件减少了外部依赖利于测试。(数据库也是一种外部组件)

  视图和表现逻辑(Controller)的分离:Controller是一个表现逻辑的组件,并非一个业务逻辑组件。MVC可以作为表现模式也可以作为建构模式,意味这Controller也可以是业务逻辑。分离逻辑和具体展示,能够对逻辑进行独立测试。

  优点:耦合性低;重用性高;生命周期成本低;部署块;可维护性高;有利软件工程化管理。

MVC框架详解可以看这位大佬的博文https://zhuanlan.zhihu.com/p/417635345

SpringMVC的主要组件

DispatcherServlet 前端控制器

前端控制器(也称中央控制器),是整体流程控制的中心,由其调用其它组件处理用户的请求,有效的降低了组件间的耦合性

HandlerMapping 处理器映射器

处理器映射器,负责根据用户请求找到对应具体的Handler处理器和拦截器,并将结果组装成一个HandlerExecutionChain返回

HandlerAdapter 处理器适配器

处理器适配器,通过它对处理器进行执行,基于适配器模式开发,如果使用@RequestMapping注解标识方法,那么执行的就是方法对象

如果处理器类上或方法上使用了@ResponseBody注解,那么就没有以下两步

视图解析器(ViewResolver)

视图解析器,将处理结果生成View视图,给方法返回的视图地址解析添加前后缀,返回完整的视图地址

视图(View)

视图,最终产出结果,常用视图如jsp、html ,视图最终渲染由前端控制器完成

SpringMVC执行流程

前端控制器拦截请求

执行doService方法

首先,当一个请求进入controller前会先被DispatcherServlet拦截,进入doService方法,下面是整个doService方法的代码:

protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        this.logRequest(request);
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap();
            Enumeration<?> attrNames = request.getAttributeNames();

            label95:
            while(true) {
                String attrName;
                do {
                    if (!attrNames.hasMoreElements()) {
                        break label95;
                    }

                    attrName = (String)attrNames.nextElement();
                } while(!this.cleanupAfterInclude && !attrName.startsWith("org.springframework.web.servlet"));

                attributesSnapshot.put(attrName, request.getAttribute(attrName));
            }
        }

        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.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);
        }

        try {
            this.doDispatch(request, response);
        } finally {
            if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
                this.restoreAttributesAfterInclude(request, attributesSnapshot);
            }

        }

    }
this.logRequest()方法

进入doService方法后,首先执行this.logRequest方法,记录请求的详细信息,包括请求的 URL、参数、头信息等。这些信息通常会被记录到日志文件中,以便于后续的分析和调试。

??

attributesSnapshot属性

attributesSnapshot属性用于存储请求的属性快照。它的作用是在请求处理过程中,捕获请求的属性状态,以便在请求处理结束后进行清理或恢复。

在HttpServletRequest对象中设置属性:
  • 将Web应用程序上下文对象设置为HttpServletRequest的属性。这允许在请求处理过程中访问应用程序上下文中的bean和其他资源。
 request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.getWebApplicationContext());
  • 将区域解析器(LocaleResolver)设置为HttpServletRequest的属性。区域解析器用于解析客户端的区域设置信息,以便在应用程序中进行国际化和本地化。
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
  • 将主题解析器(ThemeResolver)设置为HttpServletRequest的属性。主题解析器用于解析客户端的主题信息,以便在应用程序中应用不同的主题样式。
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
  • 将主题资源(ThemeSource)设置为HttpServletRequest的属性。主题资源用于提供主题相关的资源,如样式表、图片等。
request.setAttribute(THEME_SOURCE_ATTRIBUTE, this.getThemeSource());
检查flashMapManager是否为非空。

  • 如果不为空,表示存在FlashMap管理器。
  • 然后,它使用flashMapManager从请求和响应中检索并更新FlashMap。FlashMap是一种用于在重定向请求之间传递属性的数据结构。
 FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(request, response);
  • ?如果成功检索到输入的FlashMap,则将其设置为请求的属性INPUT_FLASH_MAP_ATTRIBUTE,并使用Collections.unmodifiableMap使其不可修改。
request.setAttribute(INPUT_FLASH_MAP_ATTRIBUTE, Collections.unmodifiableMap(inputFlashMap));
  • 接着,它创建一个新的空FlashMap,并将其设置为请求的属性OUTPUT_FLASH_MAP_ATTRIBUTE。
  • 最后,它将flashMapManager设置为请求的属性FLASH_MAP_MANAGER_ATTRIBUTE。
  request.setAttribute(OUTPUT_FLASH_MAP_ATTRIBUTE, new FlashMap());
  request.setAttribute(FLASH_MAP_MANAGER_ATTRIBUTE, this.flashMapManager);

FlashMapManager是Spring MVC中用于管理FlashMap的接口。FlashMap用于在重定向请求之间传递属性,通常用于在重定向后显示一次性消息或数据。FlashMapManager负责在请求之间存储和检索FlashMap,并在适当的时候清除FlashMap。具体实现可以根据需要选择,Spring提供了默认的实现,也可以自定义实现以满足特定需求。?

执行doDispatch方法

下面是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 {
            try {
                ModelAndView mv = null;
                Exception dispatchException = null;

                try {
                    processedRequest = this.checkMultipart(request);
                    multipartRequestParsed = processedRequest != request;
                    mappedHandler = this.getHandler(processedRequest);
                    if (mappedHandler == null) {
                        this.noHandlerFound(processedRequest, response);
                        return;
                    }

                    HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
                    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;
                        }
                    }

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

                    mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        return;
                    }

                    this.applyDefaultViewName(processedRequest, mv);
                    mappedHandler.applyPostHandle(processedRequest, response, mv);
                } catch (Exception var20) {
                    dispatchException = var20;
                } catch (Throwable var21) {
                    dispatchException = new NestedServletException("Handler dispatch failed", var21);
                }

                this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
            } catch (Exception var22) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
            } catch (Throwable var23) {
                this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
            }

        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else if (multipartRequestParsed) {
                this.cleanupMultipart(processedRequest);
            }

        }
    }

doDispatch方法逐行解析:

(1)将当前的HttpServletRequest对象赋值给processedRequest变量,这样可以在后续的代码中使用processedRequest来代表原始的请求对象。

(2)声明一个HandlerExecutionChain类型的变量mappedHandler,并将其初始化为null。这个变量用于存储处理请求的处理器(handler)以及相关的拦截器链。

(3)声明一个boolean类型的变量multipartRequestParsed,并将其初始化为false。这个变量通常用于跟踪是否已经解析了multipart请求。

(4)通过WebAsyncUtils工具类获取与当前请求相关的WebAsyncManager对象,用于处理异步请求的管理。

(5)声明一个类型为ModelAndView的变量mv,并将其初始化为null。ModelAndView是Spring MVC中用于将模型数据和视图名称封装在一起的类。

(6)声明一个类型为Exception的变量dispatchException,并将其初始化为null。这个变量通常用于存储在请求分派过程中发生的异常。

(7)调用checkMultipart方法对请求进行处理

检查是否为multipart请求或对multipart请求进行解析。处理后的请求对象被赋值给processedRequest变量。

processedRequest = this.checkMultipart(request);

检查处理后的请求对象是否与原始请求对象相同

  • 如果不同,则说明multipart请求已经被解析。
 multipartRequestParsed = processedRequest != request;
  • 如果相同就会执行catch语句抛出异常

(8)调用getHandler获取处理器执行链

(9.1)处理器为空报404找不到处理器异常直接返回,返回前会执行finally中的代码

  • 检查是否异步处理已经开始,即asyncManager.isConcurrentHandlingStarted()返回true。
  • 如果处理程序不为null,则调用处理程序的applyAfterConcurrentHandlingStarted方法,传入处理后的请求和响应对象,以便在并发处理开始后执行相关操作。
  • 如果异步处理未开始,它会检查是否multipart请求已经被解析,即multipartRequestParsed为true。
  • 如果是,它调用cleanupMultipart方法对处理后的请求进行清理,通常用于清理multipart请求相关的资源。

(9.2)处理器不为空,调用getHandlerAdapter()

getHandlerAdapter方法源码:

调用supports()方法

判断是够支持给定的处理器,如果找到一个适配器和处理器匹配,将返回这个适配器。

  protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            Iterator var2 = this.handlerAdapters.iterator();

            while(var2.hasNext()) {
                HandlerAdapter adapter = (HandlerAdapter)var2.next();
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }

        throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
(10)处理器执行链调用applyPreHandle()执行处理器执行链的前置拦截器

applyPreHandle方法源码:

 boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
      HandlerInterceptor[] interceptors = this.getInterceptors();
// 获取拦截器数组

if (!ObjectUtils.isEmpty(interceptors)) {
    // 如果拦截器数组不为空
    for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
        // 遍历拦截器数组
        HandlerInterceptor interceptor = interceptors[i];
        // 获取当前拦截器

        if (!interceptor.preHandle(request, response, this.handler)) {
            // 如果拦截器的preHandle方法返回false
            this.triggerAfterCompletion(request, response, (Exception)null);
            // 触发afterCompletion方法
            return false;
            // 返回false
        }
    }
}
        return true;
    }
(11)处理器适配器调用handle()方法

将处理器执行链(包括拦截器与处理器)交给适配器,适配器调用handle方法执行处理器执行链以及

(12)处理器执行链调用applyPostHandle()执行处理器执行链的后置拦截器

(13)在没有出现异常的情况下会执行以下代码:

该方法的作用:处理调度结果。它首先检查是否有异常,如果有异常则处理异常;然后根据处理结果进行渲染;最后触发请求完成后的处理。

 this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);

执行doService中的finally代码块
  1. ?首先,它使用WebAsyncUtils.getAsyncManager(request)获取请求的异步管理器,并检查是否并发处理已经开始,即isConcurrentHandlingStarted()返回false。
  2. ?然后,它检查attributesSnapshot是否不为null。
  3. ?如果上述条件都满足,它调用restoreAttributesAfterInclude方法,将之前保存的请求属性恢复到请求中。这通常用于在包含请求处理过程中恢复之前保存的请求属性。

参考文章

超详细的!!!MVC架构模式说明 - 一只码农-小俊的文章 - 知乎
https://zhuanlan.zhihu.com/p/417635345

文章来源:https://blog.csdn.net/weixin_63975657/article/details/135605061
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。