Spring MVC 源码分析

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: Spring MVC 源码分析

1. 回顾Servlet

1.1. 什么是Servlet

Servlet is an API that provides many interfaces and classes including documentation.


Servlet is an interface that must be implemented for creating any Servlet.


Servlet is a class that extends the capabilities of the servers and responds to the incoming requests. It can respond to any requests.


其主要功能在于交互式地浏览和修改数据,生成动态Web内容。狭义的Servlet是指Java语言实现的一个接口,广义的Servlet是指任何实现了这个Servlet接口的类。


1.2. Servlet工作模式

  1. 客户端发送请求至服务器;
  2. 服务器启动并调用Servlet,Servlet根据客户端请求生成响应内容并将其传给服务器;
  3. 服务器将响应返回客户端;


1.3. Servlet的工作原理

  1. Servlet接口定义了Servlet与servlet容器之间的契约。这个契约是:Servlet容器将Servlet类载入内存,并产生Servlet实例和调用它具体的方法。但是要注意的是,在一个应用程序中,每种Servlet类型只能有一个实例。
  2. 用户请求致使Servlet容器调用Servlet的service()方法,并传入一个ServletRequest对象和一个ServletResponse对象。ServletRequest对象和ServletResponse对象都是由Servlet容器(例如TomCat)封装好的,并不需要程序员去实现,程序员可以直接使用这两个对象。
  3. ServletRequest中封装了当前的Http请求,因此,开发人员不必解析和操作原始的Http数据。ServletResponse表示当前用户的Http响应,程序员只需直接操作ServletResponse对象就能把响应轻松的发回给用户。
  4. 对于每一个应用程序,Servlet容器还会创建一个ServletContext对象。这个对象中封装了上下文(应用程序)的环境详情。每个应用程序只有一个ServletContext。每个Servlet对象也都有一个封装Servlet配置的ServletConfig对象。

1.4. 源码分析

1.4.1. Servlet接口

最原始的Servlet接口有三个主要方法:init、service和destroy;

/**
 * Defines methods that all servlets must implement.
 *
 * @author  Various
 *
 * @see   GenericServlet
 * @see   javax.servlet.http.HttpServlet
 *
 */
public interface Servlet {
    /**
     * Called by the servlet container to indicate to a servlet that the 
     * servlet is being placed into service.
     */
    public void init(ServletConfig config) throws ServletException;
    /**
     * Returns a {@link ServletConfig} object, which contains
     * initialization and startup parameters for this servlet.
     */
    public ServletConfig getServletConfig();
    /**
     * Called by the servlet container to allow the servlet to respond to 
     * a request.
     */
    public void service(ServletRequest req, ServletResponse res)
  throws ServletException, IOException;
    /**
     * Returns information about the servlet, such
     * as author, version, and copyright.
     */
    public String getServletInfo();
    /**
     * Called by the servlet container to indicate to a servlet that the
     * servlet is being taken out of service. 
     */
    public void destroy();
}

1.4.2. GenericServlet抽象类

  1. GenericServlet defines a generic, protocol-independent servlet.


GenericServlet抽象类包含以下主要方法,可以看出主要还是围绕着init、service和destroy;

public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
  /**
    * Called by the servlet container to indicate to a servlet that the
    * servlet is being taken out of service.  See {@link Servlet#destroy}.
    */
   public void destroy() { }
   /**
    * Called by the servlet container to indicate to a servlet that the
    * servlet is being placed into service.  See {@link Servlet#init}.
    */
   public void init(ServletConfig config) throws ServletException {
    this.config = config;
    this.init();
   }
   /**
    * A convenience method which can be overridden so that there's no need
    * to call <code>super.init(config)</code>.
    */
   public void init() throws ServletException { }
   /**
    * Called by the servlet container to allow the servlet to respond to
    * a request.  See {@link Servlet#service}.
    */
   public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;
   /**
    * Returns the name of this servlet instance.
    */
   public String getServletName() {
       ServletConfig sc = getServletConfig();
       if (sc == null) {
           throw new IllegalStateException(
               lStrings.getString("err.servlet_config_not_initialized"));
       }
       return sc.getServletName();
   }
}


1.4.3. HttpServlet抽象类

  1. HttpServlet provides an abstract class to be subclassed to create an HTTP servlet suitable for a Web site.

  2. HttpServlet的主要内容如下,主要是提供了service方法,使得继承该类的方法无需再override。定义了doGet、doPost等一系列方法,但并未给出具体实现。
public abstract class HttpServlet extends GenericServlet implements java.io.Serializable {
   /**
    * Called by the server (via the <code>service</code> method) to
    * allow a servlet to handle a GET request. 
    */
   protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     String protocol = req.getProtocol();
     String msg = lStrings.getString("http.method_get_not_supported");
     if (protocol.endsWith("1.1")) {
         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
     } else {
         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
     }
   }
   protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     NoBodyResponse response = new NoBodyResponse(resp);
     doGet(req, response);
     response.setContentLength();
   }
   protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     String protocol = req.getProtocol();
     String msg = lStrings.getString("http.method_post_not_supported");
     if (protocol.endsWith("1.1")) {
         resp.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, msg);
     } else {
         resp.sendError(HttpServletResponse.SC_BAD_REQUEST, msg);
     }
   }
   /**
    *
    * Receives standard HTTP requests from the public
    * <code>service</code> method and dispatches
    * them to the <code>do</code><i>XXX</i> methods defined in 
    * this class. This method is an HTTP-specific version of the 
    * {@link javax.servlet.Servlet#service} method. There's no
    * need to override this method.
    */
   protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
     String method = req.getMethod();
     if (method.equals(METHOD_GET)) {
         long lastModified = getLastModified(req);
         if (lastModified == -1) {
       // servlet doesn't support if-modified-since, no reason
       // to go through further expensive logic
       doGet(req, resp);
         } else {
       long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
       if (ifModifiedSince < (lastModified / 1000 * 1000)) {
           // If the servlet mod time is later, call doGet()
                       // Round down to the nearest second for a proper compare
                       // A ifModifiedSince of -1 will always be less
           maybeSetLastModified(resp, lastModified);
           doGet(req, resp);
       } else {
           resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
       }
         }
     } else if (method.equals(METHOD_HEAD)) {
         long lastModified = getLastModified(req);
         maybeSetLastModified(resp, lastModified);
         doHead(req, resp);
     } else if (method.equals(METHOD_POST)) {
         doPost(req, resp);
     } else if (method.equals(METHOD_PUT)) {
         doPut(req, resp);  
     } else if (method.equals(METHOD_DELETE)) {
         doDelete(req, resp);
     } else if (method.equals(METHOD_OPTIONS)) {
         doOptions(req,resp);
     } else if (method.equals(METHOD_TRACE)) {
         doTrace(req,resp);
     } else {
         //
         // Note that this means NO servlet supports whatever
         // method was requested, anywhere on this server.
         //
         String errMsg = lStrings.getString("http.method_not_implemented");
         Object[] errArgs = new Object[1];
         errArgs[0] = method;
         errMsg = MessageFormat.format(errMsg, errArgs);
         resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
     }
   }
   /**
    * Dispatches client requests to the protected
    * <code>service</code> method. There's no need to
    * override this method.
    */
   public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
     HttpServletRequest request;
     HttpServletResponse  response;
     try {
         request = (HttpServletRequest) req;
         response = (HttpServletResponse) res;
     } catch (ClassCastException e) {
         throw new ServletException("non-HTTP request or response");
     }
     service(request, response);
   }
}


1.5. Servlet的局限性

Servlet如果需要给客户端返回数据,比如一个HTML文件,需要一行一行的把HTML语句给用Writer输出,早期简单的网页还能应付得住,但是随着互联网的不断发展,网站的内容和功能越来越强大,一个普通的HTML文件可能就达到好几百行,如果在采用使用Servlet去一行一行的输出HTML代码的话,将会非常的繁琐并且浪费大量的时间,且在当时,出现了PHP这种可以内嵌到HTML文件的动态语言,使得制作动态网页变得异常的简单和轻松,因此大量的程序员转上了PHP语言的道路,JAVA的份额急剧减小,当时JAVA的开发者Sun公司为了解决这个问题,也开发出了自己的动态网页生成技术,使得同样可以在HTML文件里内嵌JAVA代码,这就是现在的JSP技术。


2. Spring MVC简介

2.1. 什么是MVC

  1. MVC是模型(Model)、视图(View)、控制器(Controller)的简写,是一种软件设计规范。是将业务逻辑、数据、显示分离的方法来组织代码。MVC主要作用是降低了视图与业务逻辑间的双向耦合。
  2. Model(模型):数据模型,提供要展示的数据,因此包含数据和行为,可以认为是领域模型或JavaBean组件(包含数据和行为),不过现在一般都分离开来:Value Object(数据Dao) 和 服务层(行为Service)。也就是模型提供了模型数据查询和模型数据的状态更新等功能,包括数据和业务。
  3. View(视图):负责进行模型的展示,一般就是我们见到的用户界面,客户想看到的东西。
  4. Controller(控制器):接收用户请求,委托给模型进行处理(状态改变),处理完毕后把返回的模型数据返回给视图,由视图负责展示。 也就是说控制器做了个调度员的工作。
  5. 流程:
  6. 用户通过视图层发送请求到服务器,在服务器中请求被Controller接收;
  7. Controller调用相应的Model层处理请求,处理完毕后结果返回到Controller;
  8. Controller再根据请求处理的结果找到对应的View视图,渲染数据后最终响应给浏览器;


  1. MVC不是一种设计模式,而是一种架构模式。

1.架构模式:一个架构模式描述软件系统里的基本的结构组织或纲要;架构模式提供一些事先定义好的子系统,指定他们的责任,并给出把他们组织在一起的法则和指南;

2.设计模式:一个设计模式提供一种提炼子系统或软件系统中的组织或者他们之间的关系的纲要设计;设计模式描述普遍存在的在相互通信的组件中重复出现的结构,这种结构解决在一定的背景中的具有一般性的设计问题;


2.2. 什么是Spring MVC?


  1. Spring的web框架围绕DispatcherServlet设计。DispatcherServlet的作用是将请求分发到不同的处理器。

2.3. Spring MVC核心组件

  1. DispatcherServlet:负责调度其他组件的执行,可以降低不同组件之间的耦合性,是整个Spring MVC的核心模块;
  2. Handler:处理器,完成具体的业务逻辑,相当于Servlet;
  3. HandlerMapping:DispatcherServlet是通过 HandlerMapping把请求映射到不同的Handler;
  4. HandlerInterceptor:处理器拦截器,是一个接口,如果我们需要进行一些拦截处理,可以通过实现该接口完成;
  5. HandlerExecutionChain:处理器执行链,包括两部分内容:Handler和HandlerInterceptor(系统会有一个默认的HandlerInterceptor,如果有额外拦截处理,可以添加拦截器进行设置);
  6. HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作包括表单的数据验证、数据类型转换、把表单数据封装到POJO等,这些一系列的操作都是由HandlerAdapter完成,DispatcherServlet通过HandlerAdapter执行不同的Handler;
  7. ModelAndView:封装了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet;
  8. ViewResolver:视图解析器,DispatcherServlet通过它把逻辑视图解析为物理视图,最终把渲染的结果响应给客户端;

2.4. Spring MVC工作流程

  1. 客户端向服务器发送请求,请求被 SpringMVC 前端控制器 DispatcherServlet 捕获;
  2. DispatcherServlet 根据该 URI,调用 HandlerMapping 获得该 Handler 配置的所有相关的对象(包括 Handler 对象以及 Handler 对象对应的拦截器),最后以 HandlerExecutionChain 的形式返回;
  3. DispatcherServlet 根据获得的 Handler,选择一个合适的 HandlerAdapter;
  4. 如果成功获得 HandlerAdapter,此时将开始执行拦截器的 preHandler方法;
  5. 提取 Request 中的数据,填充 handle入参,开始执行 handle 方法,处理请求,在填充 Handler 的入参过程中,根据你的配置,Spring 将帮你做一些额外的工作:

1.HttpMessageConveter:将请求消息(如 Json、xml 等数据)转换成一个对象,将对象转换为指定的类型信息;

2.数据转换:对请求消息进行数据转换。如 String 转换成 Integer、Double 等;

3.数据格式化:对请求消息进行数据格式化。如将字符串转换成格式化数字或格式化日期等;

4.数据验证:验证数据的有效性(长度、格式等),验证结果存储到 BindingResult 或 Error 中;

6.handle方法执行完毕后,返回一个ModelAndView对象给DispatcherServlet;

7.此时将开始执行拦截器的 postHandle 方法【逆向】;

8.DispatcherServlet把获取的ModelAndView对象传给ViewResolver视图解析器,来渲染视图;

  1. DispatcherServlet把渲染后的视图返回给客户端;
  2. 渲染视图完毕执行拦截器的 afterCompletion 方法【逆向】;


3. Spring MVC源码分析

3.1. FrameworkServlet抽象类

FrameworkServlet is the base servlet for Spring’s web framework. Provides integration with a Spring application context, in a JavaBean-based overall solution.

HttpServlrtBean抽象类:Simple extension of HttpServlet which treats its config parameters (init-param entries within the servlet tag in web.xml) as bean properties.


看一下FrameworkServlet抽象类的主要方法,发现把doXXX方法都统一到doService抽象方法上了;


         


3.2. DispatcherServlet类

1.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类override了FrameworkServlet的doService方法,并将最终的处理流程放在了doDispatch方法里,该方法很值得研究,写了一些注释,接下来会详细分析带有标号的注释对应的代码。

/**

3.3. 获取HandlerExecutionChain对象

// 1. 获取HandlerExecutionChain对象
mappedHandler = getHandler(processedRequest);

来看一下getHandler方法:

/**
  1. 遍历了handlerMappings,分别调用它们的getHandler方法,找到对应的HandlerExecutionChain;
  2. HandlerExecutionChain类中主要包含以下三个属性:
// 请


在AbstractHandlerMapping抽象类中,getHandler方法首先调用了getHandlerInternal方法,通过寻找、读取配置文件,找到request对应的handler,然后调用了getHandlerExecutionChain方法,通过路径匹配,找到对应的interceptors;

3.4. 根据Handler获取HandlerAdapter

// 2. 根据Handler获取Handl


来看一下getHandlerAdapter方法:

/**
  1. 遍历handlerAdapters,找到能够处理当前 Handler 的HandlerAdapter,如果没找到会报错;

3.5. 调用拦截器的preHandle方法

//


2.来看下applyPreHandle方法:

/**


  1. 该方法首先循环调用拦截器的preHandle方法,如果某个拦截器的preHandle方法返回 false,则调用triggerAfterCompletion方法,且记录了拦截器的执行位置;
  2. 来看下triggerAfterCompletion方法:
/
  1. 该方法反向依次调用那些 preHandle 方法返回 ture 的拦截器的 afterCompletion 方法;

3.6. 通过HandlerAdapter调用Handler实际处理请求,获取ModelAndView对象


         

来看下handle方法,handle方法会走到org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod:

/**

上述方法主要做了三件事:

  1. 组装目标方法需要的参数;
org.spri


  1. 在该方法中,使用HandlerMethodArgumentResolver进行解析请求,得到参数;
  2. HandlerMethodArgumentResolver有很多实现类,归纳如下:
  3. 1687705929055.png

2.通过反射调用处理请求的目标方法,获取方法的返回值;

org.springframewo

3.对方法的返回值进行处理;

org.s


该接口有两个方法:

/*


2.该接口具有很多实现类:


归纳如下:

1687705978024.png

以其中的RequestResponseBodyMethodProcessor为例,来看下源码:

@O


  1. 注意,并不是所有的实现类的handleReturnValue方法都将结果直接返回给客户端了,有些是需要渲染的;

3.7. 调用拦截器的postHandle方法

// 5.


来看下applyPostHandle方法:

/**


  1. 逆序调用拦截器的postHandle方法;

3.8. 处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端

3.8.1. 位置

// 6.


3.8.2. processDispatchResult方法源码

/*


3.8.3. 如果有异常,进行全局异常处理

mv =


看一下processHandlerException方法:

/**


  1. 该方法主要是遍历HandlerExceptionResolver的resolveException方法来处理异常;

3.8.4. 渲染视图

render(m

看一下render方法:

/**

3.该方法主要做了两件事:


1.调用resolveViewName方法解析视图名称得到 View;


1.最终是在org.springframework.web.servlet.view.UrlBasedViewResolver#buildView中拼接了prefix和suffix;

调用render方法渲染视图;

  1. 看一下render方法,发现最终走到


org.springframework.web.servlet.view.AbstractView#renderMergedOutputModel方法中,以其实现类override的方法org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel为例:


         

3.8.5. 调用拦截器的 afterCompletion 方法

if (mappe


2.来看一下这部分源码:

/**


  1. 主要是反向调用拦截器的afterCompletion方法;
目录
相关文章
|
29天前
|
监控 Java 应用服务中间件
Spring Boot整合Tomcat底层源码分析
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置和起步依赖等特性,大大简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是其与Tomcat的整合。
51 1
|
19天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
58 2
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
33 1
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
32 1
|
2月前
|
缓存 JavaScript Java
Spring之FactoryBean的处理底层源码分析
本文介绍了Spring框架中FactoryBean的重要作用及其使用方法。通过一个简单的示例展示了如何通过FactoryBean返回一个User对象,并解释了在调用`getBean()`方法时,传入名称前添加`&`符号会改变返回对象类型的原因。进一步深入源码分析,详细说明了`getBean()`方法内部对FactoryBean的处理逻辑,解释了为何添加`&`符号会导致不同的行为。最后,通过具体代码片段展示了这一过程的关键步骤。
Spring之FactoryBean的处理底层源码分析
|
2月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
1月前
|
前端开发 Java Spring
Spring MVC源码分析之DispatcherServlet#getHandlerAdapter方法
`DispatcherServlet`的 `getHandlerAdapter`方法是Spring MVC处理请求的核心部分之一。它通过遍历预定义的 `HandlerAdapter`列表,找到适用于当前处理器的适配器,并调用适配器执行具体的处理逻辑。理解这个方法有助于深入了解Spring MVC的工作机制和扩展点。
25 0
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
60 2
|
3月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
2月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
169 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习