一、什么是MVC

MVC是模型(Model)、视图(View)、控制器(Controller)的简写,其核心思想是通过将业务逻辑、数据、显示分离来组织代码。
MVC架构

MVC又是属于java架构模式中的一种,粒度比设计模式(抽象工厂,单例等)更大,用来组织整个应用的分层结构,而设计模式解决的是“类/对象之间如何协作”这类微观问题。

二、Spring MVC的发展

2.1 Model1时代

在Model1模式下,整个web应用几乎全部用JSP页面组成,只用少量的JavaBean来处理数据库连接、访问等操作。
在这个模式下JSP既是控制层(Controller)又是表现层(View)。显然,这种模式存在很多问题。比如控制逻辑和表现逻辑混杂导致代码重用率低;前后端相互依赖导致开发效率低等等。
mvc-mode1

2.2 Model2时代

早期的JavaWeb MVC开发模式遵循的是“Java Bean(Model) + JSP(View) + Servlet(Controller)”这种开发模式,具体功能如下:

  • Model: 系统涉及的数据,也就是dao和bean。
  • View: 展示模型中的数据,只是用来展示。
  • Controller: 接受用户请求,并将请求发送至Model,最后返回数据给JSP并展示给用

mvc-model2

Model2模式下还存在很多问题,Model2的抽象和封装程度还远远不够,使用Model2进行开发时会不可避免地重复造轮子从而大大降低了程序的可维护性和复用性。

2.3 Spring MVC时代

MVC是一种架构模式,Spring MVC是一款很优秀的MVC框架,它可以帮助我们进行更简洁的Web层的开发,并且它天生与Spring框架集成。Spring MVC模式下我们一般把后端项目分为Service层(处理业务)、Dao层(数据库操作)、Entity层(实体类)、Controller层(控制层,返回数据给前端页面)。

三、核心组件以及工作原理

核心组件如下:

  • DispatcherServlet: 核心的中央处理器,负责接收请求、分发,并给予客户端响应。
  • HandlerMapping: 处理器映射器,根据URL去匹配查找能处理的Handler,并会将请求涉及到的拦截器和Handler一起封装。
  • HandlerAdapter: 处理器适配器,根据HandlerMapping找到的Handler,适配执行对应的Handler
  • Handler: 请求处理器,处理实际请求的处理器。
  • ViewResolver: 视图解析器,根据Handler返回的逻辑视图/视图解析并渲染真正的视图,并传递给DispatcherServlet响应客户端。

原理如下图:
mvc原理

流程说明(重要):

  • 客户端(浏览器)发送请求,DispatcherServlet拦截请求。
  • DispatcherServlet根据请求信息调用HandlerMappingHandlerMapping根据URL去匹配查找能处理的Handler(也就是我们平常说的Controller控制器),并会将请求涉及到的拦截器和Handler一起封装。
  • DispatcherServlet调用HandlerAdapter适配器执行Handler
  • Handler完成对用户请求的处理后,会返回一个ModelAndView对象给DispatcherServletModelAndView顾名思义,包含了数据模型及相应的视图的信息。Model是返回的数据对象,View是个逻辑上的View
  • ViewResolver会根据逻辑View查找实际的View
  • DispatcherServlet把返回的Model传给View(视图渲染)。
  • View返回给请求者(浏览器)。

上述流程是传统开发模式(JSP,ThymeLeaf)等的工作原理。然而现在主流的开发方式是前后端分离,这种情况下Spring MVC的View概念发生了一些变化。由于View通常由前端框架(Vue,React等)来处理,后端不再负责渲染页面,而是只负责提供数据,因此:

  • 前后端分离时,后端不再返回具体的视图,而是返回纯数据(通常是JSON格式),由前端负责渲染和展示。
  • View的部分在前后端分离的场景下往往不需要设置,Spring MVC的控制器只需要返回数据,不再返回ModelAdnView,而是直接返回数据,Spring会自动将其转换为JSON格式(直接使用@RestController注解或者是使用@Controller注解搭配@ResponseBody注解)。相应的,ViewResolver也将不再被使用。

四、统一异常处理

使用注解的方式进行统一异常处理,具体会使用到@ControllerAdvice+ExceptionHandler这两个注解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

@ExceptionHandler(BaseException.class) // 用户自定义
public ResponseEntity<?> handleAppException(BaseException ex, HttpServletRequest request) {
//......
}

@ExceptionHandler(value = ResourceNotFoundException.class) // 404异常
public ResponseEntity<ErrorReponse> handleResourceNotFoundException(ResourceNotFoundException ex, HttpServletRequest request) {
//......
}

// 还可以加入其他异常
}

这种异常处理方式下,会给所有或者指定的Controller织入异常处理的逻辑(AOP),当Controller中的方法抛出异常的时候,由被@ExceptionHandler注解修饰的方法进行处理。

ExceptionHandlerMethodResolvergetMappedMethod方法决定了异常具体被哪个被 @ExceptionHandler注解修饰的方法处理异常。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
//找到可以处理的所有异常信息。mappedMethods 中存放了异常和处理异常的方法的对应关系
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
// 不为空说明有方法处理异常
if (!matches.isEmpty()) {
// 按照匹配程度从小到大排序
matches.sort(new ExceptionDepthComparator(exceptionType));
// 返回处理异常的方法
return this.mappedMethods.get(matches.get(0));
}
else {
return null;
}
}

从源代码看出:getMappedMethod()会首先找到可以匹配处理异常的所有方法信息(子类异常也能被父类处理器截获),然后对其进行继承深度(父类优先级比子类低)的升序排序,最后取最具体的那一个匹配的方法(即匹配度最高的那个)。