Spring MVC 运行流程解析(含源码分析)

简介: 章节目录Spring MVC DispatcherServlet 与 HttpServlet 关系类图Spring MVC 源码分析Request 请求映射、执行、视图解析流程总结-Spring MVC 运行流程图1.

章节目录

  • Spring MVC DispatcherServlet 与 HttpServlet 关系类图
  • Spring MVC 源码分析Request 请求映射、执行、视图解析流程
  • 总结-Spring MVC 运行流程图

1.Spring MVC DispatcherServlet 与 HttpServlet 关系类图

1.1 什么是DispatcherServlet
源码注释如下所示:

Central dispatcher for HTTP request handlers/controllers, e.g. for web UI controllers or HTTP-based remote service exporters. Dispatches to registered handlers for processing a web request, providing convenient mapping and exception handling facilities.

译文如下:

DispatcherServlet 是HTTP请求处理程序/控制器的中央调度程序(将请求映射到具体处理器(handler)上 ),例如用于Web UI控制器或基于HTTP的远程服务导出器(webService),调度器会将请求路由至已经注册好的具体的hadler,使得handler可以处理执行相关的web请求,提供了请求与处理器之间的映射关系功能,其实就是路由映射功能。

1.2 什么是HttpServlet

HttpServlet 是处理相关基于Http请求的处理程序,请求的相关信息被封装成 HttpServletRequest对象,其中Service() 方法通过获取 HttpServletRequest 中的方法名 如 GETPOSTPUT等 request-method信息的获取,去invoke具体的doGet()doPost()doPut()方法,最终将执行完业务逻辑获取到的处理数据通过HttpServletResponse对象返回给客户端。所以最终request请求结果还是从HttpServlet中的service()返回的

那么这两者之间有什么关系呢?
如下图所示DispatcherServlet与HttpServlet之间的类图关系:


其中最重要的是FrameworkServlet。
源码注释如下:

Base servlet for Spring's web framework. Provides integration with
a Spring application context, in a JavaBean-based overall solution.

译文如下:

Spring web 框架中的基础Servlet,将Spring 相关的ApplicationContext 集成进来。方便我们在后期使用Spring IOC 容器中注册的各种属性的类对象。

FrameworkServlet 整合Spring WebApplicationContext 对象源码如下:

    /**
     * Initialize and publish the WebApplicationContext for this servlet.
     * <p>Delegates to {@link #createWebApplicationContext} for actual creation
     * of the context. Can be overridden in subclasses.
     * @return the WebApplicationContext instance
     * @see #FrameworkServlet(WebApplicationContext)
     * @see #setContextClass
     * @see #setContextConfigLocation
     */
    protected WebApplicationContext initWebApplicationContext() {
        WebApplicationContext rootContext =
                WebApplicationContextUtils.getWebApplicationContext(getServletContext());
        WebApplicationContext wac = null;

        if (this.webApplicationContext != null) {
            // A context instance was injected at construction time -> use it
            wac = this.webApplicationContext;
            if (wac instanceof ConfigurableWebApplicationContext) {
                ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
                if (!cwac.isActive()) {
                    // The context has not yet been refreshed -> provide services such as
                    // setting the parent context, setting the application context id, etc
                    if (cwac.getParent() == null) {
                        // The context instance was injected without an explicit parent -> set
                        // the root application context (if any; may be null) as the parent
                        cwac.setParent(rootContext);
                    }
                    configureAndRefreshWebApplicationContext(cwac);
                }
            }
        }
    ...
 return wac;
}

其中获取web应用程序上下文的代码段为:

WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());

FrameworkServlet 对 extends 自 HttpServlet 的service()方法进行了override()


override service()

super.service()即调用HttpServlet中的Service()方法
可以看到Service()方法根据request.method 去调用具体的doxxx()方法,这里FrameworkServlet 对 doxxx()方法也进行了override()。

如下为FrameworkServlet 中doGet()方法源码

override doGet()

其中的processRequest()方法源码如下所示:

    /**
     * Process this request, publishing an event regardless of the outcome.
     * <p>The actual event handling is performed by the abstract
     * {@link #doService} template method.
     */
    protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {

        long startTime = System.currentTimeMillis();
        Throwable failureCause = null;

        LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
        LocaleContext localeContext = buildLocaleContext(request);

        RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

        initContextHolders(request, localeContext, requestAttributes);

        try {
            doService(request, response);
        }
        catch (ServletException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (IOException ex) {
            failureCause = ex;
            throw ex;
        }
        catch (Throwable ex) {
            failureCause = ex;
            throw new NestedServletException("Request processing failed", ex);
        }

        finally {
            resetContextHolders(request, previousLocaleContext, previousAttributes);
            if (requestAttributes != null) {
                requestAttributes.requestCompleted();
            }

            if (logger.isDebugEnabled()) {
                if (failureCause != null) {
                    this.logger.debug("Could not complete request", failureCause);
                }
                else {
                    if (asyncManager.isConcurrentHandlingStarted()) {
                        logger.debug("Leaving response open for concurrent processing");
                    }
                    else {
                        this.logger.debug("Successfully completed request");
                    }
                }
            }

            publishRequestHandledEvent(request, response, startTime, failureCause);
        }
    }
  • 其中最重要的是doService()方法,这个doService()方法被声明为抽象方法,在DispatcherServlet 做具体实现。
    源码实现如下:
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (logger.isDebugEnabled()) {
            String resumed = WebAsyncUtils.getAsyncManager(request).hasConcurrentResult() ? " resumed" : "";
            logger.debug("DispatcherServlet with name '" + getServletName() + "'" + resumed +
                    " processing " + request.getMethod() + " request for [" + getRequestUri(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;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<String, Object>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith("org.springframework.web.servlet")) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
                //为请求设置具体的属性。
        // 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());

        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 {
                        //调用doDispatch(),将请求分配给具体的handler去处理。实际上第二节会具体分析doDispatch()方法
            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);
                }
            }
        }
    }

1.3 Spring Web 对Request的执行流程
所以请求的整个执行流程依据之前的HttpServlet知识积累(未debug)可以大致总结如下(第二节会debug源码,用来验证我们总结的这个流程)
即:

1.请求到达dispatcherServlet,(非初次请求,初次请求会涉及dispatcherServlet初始化,调用init()方法)。
2.dispatcherServlet 执行service()方法,因Dispatcher类继承FrameworkServlet,所以调用父类的service()方法。
3.service()调用FrameworkServlet 中具体的doxxx()方法
4.FrameworkServlet 具体的 doxxx()方法调用processRequest()方法。
5.processRequest()方法调用dispatcherServlet 的 doService()方法。
6.dispatcherServlet 的 doService()方法调用doDispatch()方法。

注意:上述根方法-service()方法被servlet容器 Servlet container显示调用。

2.Spring MVC 源码分析Request 请求映射、执行、视图解析流程

简单的helloword级别的web项目,搭建方式可以略过。主要是debug开始的地方我们需要确定,因为有HttpServlet源码分析的积累,那么我们直接在DispatcherServlet中的Service方法中打断点就可以了,因为DispatcherServlet继承了FrameworkServlet,FrameworkServlet对HttpServlet中的service()方法进行了override,所以程序入口断点应该打在FrameworkServlet 中的service() 方法,接下来就是实操演示:

注意:本源码分析的是Spring 4.1版本

2.0 debug的目的

了解 request 到具体 handler 的执行流程。

2.1 FrameworkServlet 中 service() 打断点

2

2.2 开启debug模式

image.png

2.3 开始debug

image.png

执行父类service()方法


image.png

执行doGet()方法


image.png

执行processRequest()方法


执行doService()方法


执行doDispatch方法


获取请求对应的handler


继续debug hm.getHandler(request)看看这其中发生了什么?
通过SimpleUrlHandlerMapping, 发现并不能获取到 对应的 handler(HandlerExcutionChain对象),
继续foreach


通过EndpointHandlerMapping, 发现并不能获取到 对应的 handler(HandlerExcutionChain对象),
继续foreach
image.png

image.png

最终我们通过RequestMappingHandlerMapping对象获取到了对应的handler对象。
可以看下handler对象是什么东东?


image.png

所以handlerExcutionChain 对象 包含有handler对象、interceptor对象。

到此我们通过requestMappingHandlerMapping 获取到了请求对应的handler。

image.png

接下来需要以handler为参数获取真正处理请求的handlerAdaptor


image.png

接下来执行 handlerAdaptor 中 handler()方法

image.png

返回mv,需要注意的是,返回mv 其实是对Controller 中业务方法的调用其实使用到了反射。

image.png
image.png
image.png

注意在返回mv之前 通过handlerExcutionChain对象可以调用applyPreHandler 方法,可以在返回mv之前做预先处理工作。

返回mv之后,可以通过handlerExcutionChain对象可以调用applyPreHandler 方法对返回的mv做修改。我们只需要实现 handlerInterceptor类并实现配置就可以了。

最后一步执行视图渲染的工作,这一步是在dispatcherServlet中完成的。


最终请求结果



注意:由于返回结果为String 类型的value,不涉及视图解析,所以render 方法并没有执行。

3.总结-Spring MVC 运行流程图

image.png
image.png

对上述流程图的解释:

  • 用户发起请求到前端控制器(Controller)
  • 前端控制器没有处理业务逻辑的能力,需要找到具体的模型对象处理(Handler),到处理器映射器(HandlerMapping)中查找Handler对象(Model)。
  • HandlerMapping返回执行链,包含了2部分内容: ① Handler对象、② 拦截器数组
  • 前端处理器通过处理器适配器包装后执行Handler对象。
  • 处理业务逻辑。
  • Handler处理完业务逻辑,返回ModelAndView对象,其中view是视图名称,不是真正的视图对象。
  • 将ModelAndView返回给前端控制器。
  • 视图解析器(ViewResolver)返回真正的视图对象(View)。
  • (此时前端控制器中既有视图又有Model对象数据)前端控制器根据模型数据和视图对象,进行视图渲染。
  • 返回渲染后的视图(html/json/xml)返回。
  • 给用户产生响应。
目录
相关文章
|
17天前
|
存储 域名解析 弹性计算
阿里云上云流程参考:云服务器+域名+备案+域名解析绑定,全流程图文详解
对于初次通过阿里云完成上云的企业和个人用户来说,很多用户不仅是需要选购云服务器,同时还需要注册域名以及完成备案和域名的解析相关流程,从而实现网站的上线。本文将以上云操作流程为核心,结合阿里云的活动政策与用户系统梳理云服务器选购、域名注册、备案申请及域名绑定四大关键环节,以供用户完成线上业务部署做出参考。
|
6月前
|
前端开发 Java 测试技术
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
本文介绍了 `@RequestParam` 注解的使用方法及其与 `@PathVariable` 的区别。`@RequestParam` 用于从请求中获取参数值(如 GET 请求的 URL 参数或 POST 请求的表单数据),而 `@PathVariable` 用于从 URL 模板中提取参数。文章通过示例代码详细说明了 `@RequestParam` 的常用属性,如 `required` 和 `defaultValue`,并展示了如何用实体类封装大量表单参数以简化处理流程。最后,结合 Postman 测试工具验证了接口的功能。
338 0
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestParam
|
6月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestBody
`@RequestBody` 是 Spring 框架中的注解,用于将 HTTP 请求体中的 JSON 数据自动映射为 Java 对象。例如,前端通过 POST 请求发送包含 `username` 和 `password` 的 JSON 数据,后端可通过带有 `@RequestBody` 注解的方法参数接收并处理。此注解适用于传递复杂对象的场景,简化了数据解析过程。与表单提交不同,它主要用于接收 JSON 格式的实体数据。
504 0
|
6月前
|
前端开发 Java 微服务
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@PathVariable
`@PathVariable` 是 Spring Boot 中用于从 URL 中提取参数的注解,支持 RESTful 风格接口开发。例如,通过 `@GetMapping(&quot;/user/{id}&quot;)` 可以将 URL 中的 `{id}` 参数自动映射到方法参数中。若参数名不一致,可通过 `@PathVariable(&quot;自定义名&quot;)` 指定绑定关系。此外,还支持多参数占位符,如 `/user/{id}/{name}`,分别映射到方法中的多个参数。运行项目后,访问指定 URL 即可验证参数是否正确接收。
327 0
|
6月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RequestMapping
@RequestMapping 是 Spring MVC 中用于请求地址映射的注解,可作用于类或方法上。类级别定义控制器父路径,方法级别进一步指定处理逻辑。常用属性包括 value(请求地址)、method(请求类型,如 GET/POST 等,默认 GET)和 produces(返回内容类型)。例如:`@RequestMapping(value = &quot;/test&quot;, produces = &quot;application/json; charset=UTF-8&quot;)`。此外,针对不同请求方式还有简化注解,如 @GetMapping、@PostMapping 等。
281 0
|
6月前
|
JSON 前端开发 Java
微服务——SpringBoot使用归纳——Spring Boot中的MVC支持——@RestController
本文主要介绍 Spring Boot 中 MVC 开发常用的几个注解及其使用方式,包括 `@RestController`、`@RequestMapping`、`@PathVariable`、`@RequestParam` 和 `@RequestBody`。其中重点讲解了 `@RestController` 注解的构成与特点:它是 `@Controller` 和 `@ResponseBody` 的结合体,适用于返回 JSON 数据的场景。文章还指出,在需要模板渲染(如 Thymeleaf)而非前后端分离的情况下,应使用 `@Controller` 而非 `@RestController`
214 0
|
2月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
189 0
|
2月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
113 0
|
2月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
169 0
|
6月前
|
监控 Shell Linux
Android调试终极指南:ADB安装+多设备连接+ANR日志抓取全流程解析,覆盖环境变量配置/多设备调试/ANR日志分析全流程,附Win/Mac/Linux三平台解决方案
ADB(Android Debug Bridge)是安卓开发中的重要工具,用于连接电脑与安卓设备,实现文件传输、应用管理、日志抓取等功能。本文介绍了 ADB 的基本概念、安装配置及常用命令。包括:1) 基本命令如 `adb version` 和 `adb devices`;2) 权限操作如 `adb root` 和 `adb shell`;3) APK 操作如安装、卸载应用;4) 文件传输如 `adb push` 和 `adb pull`;5) 日志记录如 `adb logcat`;6) 系统信息获取如屏幕截图和录屏。通过这些功能,用户可高效调试和管理安卓设备。

推荐镜像

更多
  • DNS