Spring MVC 执行流程详解:一次请求经历了什么?
引言
在现代 Web 开发中,Spring MVC 作为 Spring 框架的重要组成部分,广泛应用于构建灵活、可扩展的 Java Web 应用。作为一个基于 MVC(Model-View-Controller)架构的框架,Spring MVC 提供了强大的请求处理机制,能够帮助开发者高效地构建 Web 层逻辑。然而,对于初学者或者对底层机制不熟悉的开发者来说,Spring MVC 的执行流程往往显得神秘而复杂。
本文将深入解析 Spring MVC 的执行流程,从用户发起 HTTP 请求开始,到最终返回响应为止,详细探讨每一个环节的作用和实现原理。通过这篇文章,你将全面了解 Spring MVC 是如何一步步处理请求的,包括 DispatcherServlet 的作用、HandlerMapping 的匹配机制、Controller 的调用方式、视图解析与渲染等核心组件的工作原理。
无论你是刚刚接触 Spring MVC 的新手,还是希望深入了解其内部机制的中级开发者,本文都将为你提供清晰的知识体系和实践指导。
一、Spring MVC 简介与基本概念
1.1 什么是 Spring MVC?
Spring MVC 是 Spring 框架中的一个模块,专门用于构建 Web 应用程序。它基于经典的 Model-View-Controller(MVC)设计模式,旨在分离业务逻辑、数据模型和用户界面,从而提高代码的可维护性和可测试性。
在 Spring MVC 中:
- Model(模型):负责封装应用的数据和业务逻辑。
- View(视图):负责展示数据,通常为 HTML 页面或 JSON 数据。
- Controller(控制器):接收用户的请求,协调 Model 和 View,完成业务逻辑并返回适当的视图。
1.2 Spring MVC 的核心组件
Spring MVC 的运行依赖于多个核心组件协同工作,主要包括:
- DispatcherServlet:前端控制器,是整个请求处理的核心入口。
- HandlerMapping:处理器映射器,负责根据请求 URL 找到对应的 Controller。
- Controller:实际处理请求的类,通常使用
@Controller
或@RestController
注解标记。 - ViewResolver:视图解析器,负责将逻辑视图名解析为实际的视图对象(如 JSP、Thymeleaf 等)。
- View:视图对象,负责将模型数据渲染成客户端可识别的格式(如 HTML、JSON 等)。
- HandlerAdapter:处理器适配器,负责调用具体的 Controller 方法。
- HandlerExceptionResolver:异常解析器,用于处理 Controller 抛出的异常。
- LocaleResolver:本地化解析器,用于支持多语言。
- ThemeResolver:主题解析器,用于支持不同的 UI 主题。
这些组件构成了 Spring MVC 的骨架,它们之间通过一定的流程进行协作,从而完成一次完整的请求处理。
二、Spring MVC 的执行流程概览
Spring MVC 的执行流程可以分为以下几个主要步骤:
- 用户发送 HTTP 请求 到服务器。
- DispatcherServlet 接收请求,作为前端控制器,它是所有请求的统一入口。
- HandlerMapping 查找对应的 Controller,根据请求的 URL 匹配合适的处理器。
- HandlerAdapter 调用 Controller,执行具体的业务逻辑。
- Controller 返回 ModelAndView 对象,包含模型数据和视图信息。
- ViewResolver 解析视图名称,找到实际的视图资源(如 JSP 文件)。
- View 渲染模型数据,生成最终的响应内容(如 HTML 页面)。
- DispatcherServlet 将响应返回给客户端。
接下来我们将逐个步骤进行详细分析。
三、DispatcherServlet:前端控制器
3.1 什么是 DispatcherServlet?
DispatcherServlet
是 Spring MVC 的核心组件之一,它是 Servlet 的子类,继承自 HttpServlet
。它的职责是接收所有的 HTTP 请求,并协调其他组件完成整个请求的处理过程。
在传统的 Web 应用中,每个请求都可能由不同的 Servlet 处理。而在 Spring MVC 中,所有的请求都会首先被 DispatcherServlet
拦截,然后由它决定如何分发和处理这些请求。
3.2 DispatcherServlet 的配置
在 web.xml
中,我们需要配置 DispatcherServlet
并指定其映射路径。例如:
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
在这个例子中,我们配置了一个名为 dispatcher
的 DispatcherServlet
,并将其映射到 /
路径,表示它可以处理所有进入的请求。contextConfigLocation
参数指定了 Spring 配置文件的位置。
3.3 DispatcherServlet 的初始化
当 Web 容器(如 Tomcat)启动时,DispatcherServlet
会加载其关联的 Spring 配置文件(通常是 <servlet-name>-servlet.xml
),并初始化一系列的组件,如 HandlerMapping、ViewResolver、HandlerAdapter 等。
这些组件在后续的请求处理过程中将发挥关键作用。
四、HandlerMapping:处理器映射器
4.1 HandlerMapping 的作用
HandlerMapping
的作用是根据请求的 URL 找到对应的 Controller 类和方法。Spring MVC 支持多种类型的 HandlerMapping,最常见的是:
- BeanNameUrlHandlerMapping:根据 Bean 名称来映射 URL。
- SimpleUrlHandlerMapping:手动配置 URL 与 Controller 的映射关系。
- RequestMappingHandlerMapping:基于注解的方式(如
@RequestMapping
、@GetMapping
、@PostMapping
等)来匹配 URL。
其中,RequestMappingHandlerMapping
是默认使用的 HandlerMapping,它会扫描所有带有 @RequestMapping
注解的类和方法,并建立 URL 与 Controller 的映射关系。
4.2 HandlerMapping 的工作机制
当 DispatcherServlet
接收到请求后,会调用 getHandler()
方法获取对应的处理器(Handler)。这个方法会遍历所有的 HandlerMapping 实例,直到找到第一个能够处理该请求的 Handler。
以 RequestMappingHandlerMapping
为例,它会检查请求的 URL 是否与某个 @RequestMapping
注解的方法匹配。如果匹配成功,则返回一个封装了目标 Controller 及其方法的 HandlerExecutionChain
对象。
例如:
@GetMapping("/hello")
public String sayHello() {
return "hello";
}
当访问 /hello
时,RequestMappingHandlerMapping
会找到这个方法,并将其封装为一个 Handler。
五、HandlerAdapter:处理器适配器
5.1 HandlerAdapter 的作用
HandlerAdapter
的作用是调用具体的 Controller 方法,并处理其返回值。由于 Spring MVC 支持多种类型的 Controller(如基于注解的 Controller、基于接口的 Controller 等),因此需要不同的 HandlerAdapter 来适配不同类型的处理器。
常见的 HandlerAdapter 包括:
HttpRequestHandlerAdapter
SimpleControllerHandlerAdapter
AnnotationMethodHandlerAdapter
(旧版本)RequestMappingHandlerAdapter
(新版本)
目前最常用的是 RequestMappingHandlerAdapter
,它用于处理基于注解的 Controller 方法。
5.2 HandlerAdapter 的调用流程
一旦 DispatcherServlet
获取到了 Handler,它就会调用 getHandlerAdapter()
方法,找到能够处理当前 Handler 的适配器。随后调用 handle()
方法,真正执行 Controller 中的方法。
例如,在 RequestMappingHandlerAdapter
内部,会调用 invokeHandlerMethod()
方法来反射执行 Controller 方法,并处理参数绑定、返回值处理等工作。
六、Controller:处理请求的逻辑中心
6.1 Controller 的定义方式
在 Spring MVC 中,Controller 可以通过以下几种方式定义:
- 使用
@org.springframework.stereotype.Controller
注解标注的类。 - 使用
@org.springframework.web.bind.annotation.RestController
注解,适用于 RESTful API。 - 实现
Controller
接口(不推荐)。 - 实现
HttpRequestHandler
接口(也不推荐)。
推荐使用 @Controller
或 @RestController
,因为它们更简洁、易读,并且与 Spring 的自动扫描机制兼容。
6.2 Controller 的请求处理
Controller 的方法可以通过各种注解来指定请求类型、URL 映射、参数绑定等行为,例如:
@RestController
public class HelloController {
@GetMapping("/hello")
public String sayHello(@RequestParam String name) {
return "Hello, " + name;
}
}
在这个例子中:
@RestController
表示这是一个 REST 控制器,返回值直接写入 HTTP 响应体。@GetMapping("/hello")
表示该方法处理 GET 请求,路径为/hello
。@RequestParam String name
表示从请求参数中提取name
参数。
当用户访问 /hello?name=Tom
时,Spring MVC 会自动将 name
参数注入到方法中,并返回 "Hello, Tom"
。
七、ModelAndView:模型与视图的载体
7.1 ModelAndView 的结构
ModelAndView
是 Spring MVC 中用于封装模型数据和视图信息的对象。它包含两个部分:
- Model:存储模型数据,通常是一个 Map 结构,键值对形式。
- View:表示视图信息,可以是视图名称或实际的视图对象。
7.2 ModelAndView 的创建与使用
Controller 可以通过返回 ModelAndView
对象来同时传递模型数据和指定视图。例如:
@Controller
public class UserController {
@GetMapping("/user/{id}")
public ModelAndView getUser(@PathVariable Long id) {
User user = userService.findById(id);
ModelAndView modelAndView = new ModelAndView("userDetail");
modelAndView.addObject("user", user);
return modelAndView;
}
}
在这个例子中,userDetail
是视图名称,modelAndView.addObject("user", user)
将用户对象添加到模型中。
八、ViewResolver:视图解析器
8.1 ViewResolver 的作用
ViewResolver
的作用是将逻辑视图名解析为实际的视图对象。Spring MVC 提供了多种内置的 ViewResolver,如:
InternalResourceViewResolver
:用于解析 JSP 视图。ThymeleafViewResolver
:用于解析 Thymeleaf 模板。FreeMarkerViewResolver
:用于解析 FreeMarker 模板。VelocityViewResolver
:用于解析 Velocity 模板。
8.2 ViewResolver 的配置与使用
以 InternalResourceViewResolver
为例,通常在 Spring 配置文件中这样配置:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/" />
<property name="suffix" value=".jsp" />
</bean>
这样,当 Controller 返回 "userDetail"
时,ViewResolver
会将其解析为 /WEB-INF/views/userDetail.jsp
。
九、View:视图的渲染
9.1 View 的作用
View
是 Spring MVC 中用于渲染模型数据的实际视图对象。它负责将模型数据转换为客户端可以理解的格式(如 HTML、JSON 等)。
常见的 View 实现包括:
JstlView
:用于渲染 JSP 页面。JsonView
:用于输出 JSON 格式的数据。AbstractTemplateView
:用于模板引擎(如 Thymeleaf、Freemarker)。
9.2 View 的渲染过程
当 ViewResolver
找到对应的 View
后,DispatcherServlet
会调用 render()
方法,将模型数据传入视图进行渲染。
例如,对于 JSP 页面,模型数据会被放入 request.setAttribute()
中,然后通过 JSP EL 表达式显示出来:
<h1>User Detail</h1>
<p>Name: ${user.name}</p>
<p>Email: ${user.email}</p>
十、异常处理与错误页面
10.1 HandlerExceptionResolver 的作用
HandlerExceptionResolver
是 Spring MVC 中用于处理 Controller 抛出异常的组件。它可以在发生异常时返回特定的视图或错误码。
常见的实现类包括:
SimpleMappingExceptionResolver
:根据异常类型映射到特定视图。ResponseStatusExceptionResolver
:处理带有@ResponseStatus
注解的异常。DefaultHandlerExceptionResolver
:处理 Spring 自身抛出的标准异常。
10.2 全局异常处理(@ControllerAdvice)
从 Spring 3.2 开始,可以使用 @ControllerAdvice
注解实现全局异常处理:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ModelAndView handleResourceNotFound() {
ModelAndView modelAndView = new ModelAndView("error/404");
return modelAndView;
}
@ExceptionHandler(Exception.class)
public ModelAndView handleGeneralError() {
ModelAndView modelAndView = new ModelAndView("error/general");
return modelAndView;
}
}
这样可以集中处理异常,避免在每个 Controller 中重复编写异常处理逻辑。
十一、国际化支持(LocaleResolver)
11.1 LocaleResolver 的作用
LocaleResolver
用于确定当前请求的语言环境(Locale),从而支持多语言切换。常见的实现类包括:
AcceptHeaderLocaleResolver
:基于 HTTP 请求头中的Accept-Language
字段。CookieLocaleResolver
:基于 Cookie 中保存的 Locale。SessionLocaleResolver
:基于 Session 中保存的 Locale。
11.2 配置 LocaleResolver
在 Spring 配置文件中配置:
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="en"/>
</bean>
还可以配合 LocaleChangeInterceptor
实现动态切换语言:
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"/>
</bean>
</mvc:interceptors>
这样,用户可以通过 ?lang=en
或 ?lang=zh
来切换语言。
十二、拦截器(HandlerInterceptor)
12.1 拦截器的作用
拦截器(HandlerInterceptor
)允许我们在请求到达 Controller 之前或之后执行一些通用逻辑,例如权限验证、日志记录、性能监控等。
12.2 拦截器的生命周期
拦截器有三个主要方法:
preHandle()
:在 Controller 方法执行前调用,返回值为布尔值,决定是否继续执行后续流程。postHandle()
:在 Controller 方法执行后调用,但视图尚未渲染。afterCompletion()
:在整个请求完成后调用,可用于释放资源。
12.3 自定义拦截器示例
@Component
public class LoggingInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Request URL: " + request.getRequestURL());
return true; // 继续执行
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Post Handle method called.");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Request completed.");
}
}
在配置文件中注册拦截器:
<mvc:interceptors>
<bean class="com.example.LoggingInterceptor"/>
</mvc:interceptors>
十三、总结:一次请求的完整流程回顾
现在我们已经详细介绍了 Spring MVC 的各个核心组件及其作用。为了加深理解,我们可以再次回顾一次完整的请求流程:
- 用户在浏览器中输入 URL,发送 HTTP 请求。
- 请求被
DispatcherServlet
拦截。 DispatcherServlet
调用HandlerMapping
,查找匹配的 Controller。- 找到对应的 Controller 后,
DispatcherServlet
调用HandlerAdapter
来执行 Controller 方法。 - Controller 方法执行完毕后返回
ModelAndView
。 ViewResolver
解析视图名称,定位实际的视图资源。View
渲染模型数据,生成 HTML、JSON 等响应内容。- 最终响应通过
DispatcherServlet
返回给客户端。 - 整个过程中还可能涉及异常处理、拦截器、国际化等功能。
十四、结语
通过对 Spring MVC 执行流程的深入剖析,我们可以更好地理解其背后的机制和组件之间的协作关系。掌握这些知识不仅有助于我们写出更高效的代码,也能在调试和优化性能时提供有力的支持。
如果你正在学习 Spring MVC 或者正在开发企业级 Web 应用,建议你结合源码阅读和实际项目经验,进一步加深对这些组件的理解。随着你对 Spring 生态系统的不断深入,你会发现 Spring MVC 的强大之处远不止于此。