Spring MVC


Spring MVC

1 概念梳理

1.1 MVC 框架

MVC 全名是 Model View Controller ,是 模型(model)-视图(view)-控制器(controller) 的缩写,一种软件设计典范。

用一种业务逻辑、数据、界面显示分离的方法组织代码,将业务逻辑聚集到一个部件里面,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。

Model(模型):表示领域信息的对象,包含除了用于 UI 部分的所有数据和行为。可以简单理解为后端的 Service 层和 Dao 层。

View(视图):表示 UI 中模型的显示。可以简单理解为前端页面,包括 HTML 静态页面,JSP 动态页面等。

Controller(控制器):接收用户输入,操作模型,并使视图更新。可以简单理解为后端的 Web 交互层。

MVC 框架

1.2 Controller

Controller 即是 MVC 中的 C ,使用 Controller 的目的是将 M 和 V 的实现代码分离,从而使同一个程序可以使用不同的表现形式。

1.2.1 传统 Controller

  • 页面控制器(Page Controller)用来解决路径名和文件之间复杂的对应关系。Page Controller 在 Web 站点上为每个页面都准备了一个输入控制器,这个控制器可能就是页面本身,也可能是对应这个页面的独立对象。

  • 前端控制器(Front Controller)让所有的请求都经过一个 Handler 对象,这个对象能够进行一些公共的处理,例如验证信息、简单预处理、跨域请求等。

1.2.2 Spring MVC 的 Controller

Spring MVC 结合 Front Controller 模式以及 Page Controller 模式,将单一的 Servlet 作为整个应用的前端控制器 DispatcherServlet ,该 Servlet 接收到具体的 Web 请求之后,按照可配置的映射信息,将待处理的 Web 请求转发给次级控制器处理:( sub-controller 即 org.springframework.web.servlet.mvc.Controller )

1.3 Spring MVC 中的基本概念

1.3.1 本文出现的词汇大全

这里的中文名都是个人命名词汇,可能与其他描述有偏差,文章中的名词均以这里为准。

中文名 类名 简介
前端控制器 org.springframework.web.servlet.DispatcherServlet Spring MVC 的核心控制器,负责 Spring MVC 的流程管理
处理器 处理器类型很多,最常用的是 org.springframework.web.method.HandlerMethod 最终执行单元,所有的业务逻辑与业务数据都在处理器中
处理器映射器 org.springframework.web.servlet.HandlerMapping 将请求中的 URL 映射到某个处理器的方法
处理链 org.springframework.web.servlet.HandlerExecutionChain 一个处理器+多个拦截器组成的执行编排
处理器适配器 org.springframework.web.servlet.HandlerAdapter 从请求中解析数据适配处理器并执行处理器的方法
模型视图 org.springframework.web.servlet.ModelAndView 处理器的一种执行结果,包含一组模型和一个视图,现在已经很少使用了
视图解析器 org.springframework.web.servlet.ViewResolver 视图解析器,用于将视图名解析为视图

1.3.2 什么是处理器?

Spring MVC 中的处理器(Handler)就是上文提到的次级控制器( sub-controller 即 org.springframework.web.servlet.mvc.Controller ),是我们需要手动开发的业务逻辑。

一般来说处理器包含待执行的方法方法所在的 Bean 以及方法的参数类型

最常用的处理器是 org.springframework.web.method.HandlerMethod ,其定义如下:

protected final Log logger = LogFactory.getLog(getClass());
//方法所在的bean
private final Object bean;
//Spring的bean工厂
private final BeanFactory beanFactory;
//bean的类型
private final Class<?> beanType;
//关联的方法
private final Method method;
//桥接方法简单来说就是继承了一个范型类或者实现了一个范型接口的方法, 编译器生成bridgeMethod的目的就是为了和jdk1.5之前的字节码兼容
private final Method bridgedMethod;
//当前方法的参数
private final MethodParameter[] parameters;

1.3.3 什么是处理链?

Spring MVC 不仅要执行处理器中包含的方法,它自己也有许多工作,例如执行多种拦截器、异常处理等。所有这些方法都会聚合到一个处理器链当中。所以处理器链通常包含一个处理器多个拦截器

org.springframework.web.servlet.HandlerExecutionChain 的定义如下:

public class HandlerExecutionChain {
//处理器
private final Object handler;
//拦截器链
private HandlerInterceptor[] interceptors;

2 Spring MVC 中请求的生命周期

Spring MVC 主流程主要有三个阶段:

  • 处理器映射阶段(HandlerMapping):通过解析 URL 中的路径来找到对应的处理器。

  • 处理器适配阶段(HandlerAdapter):通过解析请求中的参数来适配处理器,并执行处理器。

  • 视图解析阶段(ViewResolver):如果处理器返回的是逻辑视图,那么会在这个阶段绘制成可展示的视图。

而前端控制器 DispatcherServlet 统筹整个流程,它的工作除了流程扭转之外,还有执行各种拦截器以及统一异常管理等。

2.1 Spring MVC 生命周期总控——DispatcherServlet

首先,让我们来直观了解一下 DispatcherServlet 的主流程,同时也是 Spring MVC 中请求的生命周期。

Spring MVC 生命周期

上图中不带有颜色的处理块的工作都是 DispatcherServlet 负责的。主要是拦截器的前置操作、后置操作、完成/异常操作以及最终完成操作。并且还负责统一管理主流程中出现的异常。而蓝色块的工作属于处理器映射器,红色块的工作属于处理器适配器,绿色块的工作属于视图解析器。接下来,我们就着重研究这三个带有颜色的阶段。

2.2 处理器映射阶段——HandlerMapping

处理器映射阶段主要涉及到的是处理器映射器 HandlerMapping 。

处理器映射器的工作就是解析请求 URL 中的 PATH 并找到与之对应的处理器。

所有实现了接口 HandlerMapping 的类都是一个处理器映射器,HandlerMapping 只有一个方法:

public interface HandlerMapping {
    //根据请求获得处理链,处理链包含处理器和拦截器链
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

一般来说,开发者不会去实现处理器映射器,因为映射器没有什么业务定制的逻辑,并且 Spring MVC 源生的映射器功能就很强大。

映射器一般分为两种,一种是将路径映射到类上的 Bean 映射器,另一种是将路径映射到方法上的 Method 映射器。

Bean 映射器的代表是 SimpleUrlHandlerMapping 和 BeanNameUrlHandlerMapping ,但是这些都是老掉牙的映射器了,目前已经没人使用了。

Method 映射器的代表是 DefaultAnnotationHandlerMapping 和 RequestMappingHandlerMapping 。目前使用最多的就是 RequestMappingHandlerMapping 。

2.2.1 常用的处理器映射器——RequestMappingHandlerMapping

看名字我们就能联想起一个最常用的注解 @RequestMapping ,没错 RequestMappingHandlerMapping 就是作用于 @RequestMapping 注解上的方法级映射器。

处理器映射器其实挺简单的,这里贴一张流程图就基本能描述清楚了。

流程图

现在有两个问题:1、会不会出现一个路径映射到多个处理器的情况? 2、如果问题 1 成立,既然映射器是串行执行的,那么哪个映射器在前?

问题1答案:一个路径是可以映射到多个处理器上的。但是前提是这多个处理器存在于不同的映射器。

例如使用 RequestMappingHandlerMapping 注册了一个路径为 /a/b 的处理器 A ,也可以同时在 SimpleUrlHandlerMapping 中注册一个路径为 /a/b 的处理器 B 。

问题2答案:既然允许问题 1 这种看似 BUG 的情况出现,那么 Spring MVC 自然有解决方法,因为每个映射器之间是有优先级的。

继承关系中表明了每个映射器都继承了 Ordered 接口的。并且在初始化的时候对于所有的映射器执行了 AnnotationAwareOrderComparator.*sort*(this.handlerMappings); 这个方法进行排序。

粗略记录了一下常用映射器的优先级:RequestMappingHandlerMapping(order=0,优先级最高) > BeanNameUrlHandlerMapping(order=2,优先级次高) > SimpleUrlHandlerMapping(order=Integer.Max,优先级最低)

2.3 处理器适配阶段——HandlerAdapter

处理器适配阶段主要涉及到的是适配器 HandlerAdapter 。

处理器适配器的工作就是从请求中解析处理器需要的参数,并执行处理器。

所有实现了接口 HandlerAdapter 的类都是一个处理器适配器,HandlerAdapter 有三个方法:

public interface HandlerAdapter {
    //表示适配器支持哪些处理器,例如RequestMappingHandlerAdapter就支持HandlerMethod处理器
    boolean supports(Object handler);
    //执行处理器
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
    //LastModified是基于“时间戳”的缓存机制,属于HTTP协议中的缓存机制。实现类可以简单返回-1表示该适配器不支持缓存。
    //感兴趣的同学可以自己研究,这里就不做解释了。因为在我们常用的RequestMappingHandlerAdapter中永久返回-1,表示不支持。
    long getLastModified(HttpServletRequest request, Object handler);
}

我们可以看到适配器多了一个 supports 的方法,表明它支持哪些处理器类型。每一个处理器映射器都会有一个配套的处理器适配器,它们之间就是用处理器的类型关联的。

例如 RequestMappingHandlerMapping 返回的处理器类型就是 HandlerMethod ,所以它配套的适配器 RequestMappingHandlerAdapter 的支持类型就是 HandlerMethod 。

接下来我们看一下适配阶段最重要的工作——参数解析是怎么完成的。

2.3.1 参数解析

参数解析主要涉及到的是参数解析器(ArgumentResolver)。

任何实现了 HandlerMethodArgumentResolver 接口的类均为参数解析器,参数解析器包含 Spring 自带的参数解析器以及开发者自定义的参数解析器。其接口定义如下:

public interface HandlerMethodArgumentResolver {
    //支持的类型
    boolean supportsParameter(MethodParameter parameter);
    //解析参数
    Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
}

2.3.2 常用参数解析器——RequestParamMethodArgumentResolver

让我们挑一个最常见的参数解析器来研究一下,当方法的参数被 @RequestParam 注解修饰的时候,就会进入 RequestParamMethodArgumentResolver 进行解析。

RequestParamMethodArgumentResolver 的参数解析流程图,其中红色线表示在我们项目中绝大多数参数解析的流程:

目前我们开发使用到的参数基本都是由 ConversionService 进行解析的了。ConversionService 是 spring-core 包中专门进行类型转换的工具,十分强大。

2.4 视图解析阶段——ViewResolver

在第二节一开始的生命周期图中 DispatcherServlet 总是返回视图给用户的意思现在并不准确了。目前更多的是 DispatcherServlet 返回数据给前端,前端渲染视图再给用户。
所以在如今的前后端分离的场景中,Spring MVC 已经名存实亡,更多的是 Spring MC 。这里就不深入探讨视图解析阶段了。

3 常用的注解与接口

3.1 @ResponseBody 与 @RequestParam

  • @RequestParam
    用来处理 Content-Type: application/x-www-form-urlencoded 编码的内容。(HTTP 协议中,如果不指定 Content-Type ,则默认传递的参数就是 application/x-www-form-urlencoded 类型)
    RequestParam 可以接受简单类型的属性,也可以接受对象类型。 实质是将 Request.getParameter() 中的 Key-Value 参数 Map 利用 Spring 的转化机制 ConversionService 配置,转化成参数接收对象或字段。
  • @RequestBody
    用来处理非 Content-Type: application/x-www-form-urlencoded 编码格式的数据。
    GET 请求中,因为没有 HttpEntity ,所以 @RequestBody 并不适用。
    POST 请求中,通过 HttpEntity 传递的参数,必须要在请求头中声明数据的类型 Content-Type ,Spring MVC 通过使用 HandlerAdapter 配置的 HttpMessageConverters 来解析 HttpEntity 中的数据,然后绑定到相应的 Bean 上。
  • @RequestParam 可以与 @RequestBody 同时使用,使用时需要谨慎,因为 @RequestBody 是 case 重灾区。

3.2 拦截器——HandlerInterceptor

所有实现了 HandlerInterceptor 接口的类都是一个拦截器,HandlerInterceptor 的定义如下:

public interface HandlerInterceptor {
    //适配前置处理器,在适配器执行之前调用。其返回值表示是否通过拦截器,如果返回false则直接中断进入该拦截器的完成后置处理器afterCompletion,并将参数中的response进行返回
    boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
    //适配后置处理器,在适配器执行之后调用。
    void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception;
    //完成后置处理器,在绘制视图之后调用 or 主流程发生异常时调用
    void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)throws Exception;
}

public interface AsyncHandlerInterceptor extends HandlerInterceptor {
    //最终完成处理器,在主流程完全结束前调用,类似于try-catch中的finally,确实也是这么实现的
    void afterConcurrentHandlingStarted(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception;
}

拦截器的时间轴如下图,分别为非中断情况、前置拦截器中断情况以及异常中断情况:

这里有两个重点:

  1. 拦截器链的 preHandle 的执行顺序是从 1->N 的,其余的 postHandle、afterCompletion 以及 afterConcurrentHandlingStarted 的执行顺序都是从 N->1 的。

  2. 假设第 N 的前置拦截器 preHandle 返回 false ,那么它会直接跳转到 afterCompletion 阶段,并且从第 N-1 个拦截器的 afterCompletion 执行到第 1 个拦截器的 afterCompletion 。

3.3 异常处理器——HandlerExceptionResolver

所有实现了 HandlerExceptionResolver 接口的类都是一个异常处理器。HandlerExceptionResolver 的源码如下:

public interface HandlerExceptionResolver {
    //异常处理器在处理异常之后需要返回一个异常状态下的ModelAndView(通常是错误页),也可以是null
    //当项目存在多个异常处理器时,只要其中一个异常处理器返回的ModelAndView不是null时,就会中断异常处理器链的执行
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
}

从第二节的主流程图中我们可以看到,异常处理是在视图绘制之前进行的,这样可以让我们异常处理器返回的视图也进行相同的绘制流程。而这里的异常捕捉的是绘制视图之前的流程中产生的异常:

但是这里需要注意的是,多个异常处理器是串行执行的,只要有一个异常处理器返回的 ModelAndView 不是 null ,执行就结束了。

4 参考


文章作者: wenjun
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 wenjun !
评论
 上一篇
Spring常见问题总结 Spring常见问题总结
Spring 常见问题总结1. 什么是 Spring 框架?Spring 是一种轻量级开发框架,旨在提高开发人员的开发效率以及系统的可维护性。 我们一般说 Spring 框架指的都是 Spring Framework ,它是很多模块的集合,
2020-06-07
下一篇 
Spring IoC Spring IoC
Spring IoC概念梳理什么是 IoC 和 DI ?IoC :控制反转(Inversion of Control),这不是什么技术,而是一种设计思想。在 Java 开发中,IoC 意味着将你设计好的对象交给容器控制,而不是传统的在你的对
2020-05-26
  目录