跳到主要内容

Spring MVC 框架详解

Spring MVC 是 Spring Framework 的一个重要模块,是一个基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架。本文将从核心概念、工作流程、关键组件到实战面试题全面解析 Spring MVC。

📋 目录

Spring MVC 概述

什么是 Spring MVC

Spring MVC(Model-View-Controller)是一个基于 Java 的 Web 框架,它遵循 MVC 设计模式,用于开发灵活和松耦合的 Web 应用程序。

核心优势

  1. 角色分离清晰:Model、View、Controller 各司其职
  2. 灵活性强:支持多种视图技术(JSP、Thymeleaf、FreeMarker等)
  3. 与 Spring 生态无缝集成:充分利用 Spring 的 IoC 和 AOP 特性
  4. 测试友好:易于进行单元测试和集成测试

核心架构与工作流程

工作流程图

用户请求 → DispatcherServlet → HandlerMapping → Controller → Model
↑ ↓
← ViewResolver ← View ← ModelAndView ← Service ← Repository ← DAO

详细执行步骤

  1. 用户发送请求:用户通过浏览器发送 HTTP 请求
  2. DispatcherServlet 接收请求:前端控制器接收所有请求并进行分发
  3. HandlerMapping 查找处理器:根据请求 URL 找到对应的 Controller 方法
  4. HandlerAdapter 适配处理器:适配并调用具体的 Controller 方法
  5. Controller 处理请求:执行业务逻辑,返回 ModelAndView
  6. ViewResolver 解析视图:根据视图名称解析具体的视图对象
  7. View 渲染视图:将模型数据渲染到视图中
  8. 返回响应:将渲染结果返回给用户

核心组件详解

1. DispatcherServlet(前端控制器)

作用:Spring MVC 的核心,作为统一的入口点处理所有 HTTP 请求。

特点

  • 继承自 FrameworkServlet
  • 负责协调各个组件的工作
  • 使用单例模式

配置示例

<!-- web.xml -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

2. HandlerMapping(处理器映射器)

作用:建立请求 URL 与 Controller 方法之间的映射关系。

常用实现

  • BeanNameUrlHandlerMapping:基于 Bean 名称
  • SimpleUrlHandlerMapping:基于 URL 配置
  • RequestMappingHandlerMapping:基于注解

3. Controller(控制器)

作用:处理具体请求,执行业务逻辑,返回 ModelAndView。

接口定义

public interface Controller {
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception;
}

4. ViewResolver(视图解析器)

作用:将逻辑视图名解析为具体的视图对象。

常用实现

  • InternalResourceViewResolver:用于 JSP
  • ThymeleafViewResolver:用于 Thymeleaf
  • FreeMarkerViewResolver:用于 FreeMarker
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>

5. ModelAndView

作用:封装模型数据和视图信息。

ModelAndView mv = new ModelAndView();
mv.addObject("user", user);
mv.setViewName("user/detail");
return mv;

常用注解详解

@Controller

标记一个类为 Spring MVC 控制器。

@Controller
public class UserController {
// 控制器方法
}

@RequestMapping

配置请求映射规则。

// 基本用法
@RequestMapping("/user")
public class UserController {

@RequestMapping("/list")
public String list() {
return "user/list";
}

// 指定请求方法
@RequestMapping(value = "/save", method = RequestMethod.POST)
public String save(User user) {
return "redirect:/user/list";
}

// 支持通配符
@RequestMapping("/user/*/info")
public String userInfo() {
return "user/info";
}
}

@RestController

组合注解,相当于 @Controller + @ResponseBody。

@RestController
@RequestMapping("/api")
public class UserApiController {

@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping

RESTful 风格的请求映射注解。

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
return userService.findById(id);
}

@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}

@RequestParam

获取请求参数。

@RequestMapping("/search")
public String search(@RequestParam("keyword") String keyword,
@RequestParam(defaultValue = "1") int page) {
// 处理搜索逻辑
return "search/result";
}

@PathVariable

获取 URL 路径变量。

@GetMapping("/users/{id}")
public String getUser(@PathVariable("id") Long userId, Model model) {
User user = userService.findById(userId);
model.addAttribute("user", user);
return "user/detail";
}

@RequestBody

获取请求体中的 JSON 数据。

@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}

@ResponseBody

将返回值直接写入 HTTP 响应体。

@GetMapping("/api/users")
@ResponseBody
public List<User> getUsers() {
return userService.findAll();
}

配置与使用

XML 配置方式

<!-- spring-mvc.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 启用注解驱动 -->
<mvc:annotation-driven/>

<!-- 扫描 Controller 包 -->
<context:component-scan base-package="com.example.controller"/>

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>

<!-- 静态资源处理 -->
<mvc:resources mapping="/static/**" location="/static/"/>

</beans>

Java 配置方式

@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {

@Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}

@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/");
}

@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}

RESTful API 开发

RESTful 设计原则

  1. 使用 HTTP 动词表示操作:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)
  2. 使用名词表示资源
  3. 使用合适的 HTTP 状态码
  4. 统一的响应格式

实际案例

@RestController
@RequestMapping("/api/v1/users")
public class UserApiController {

@Autowired
private UserService userService;

// 查询所有用户
@GetMapping
public ResponseEntity<ApiResponse<List<User>>> getAllUsers() {
List<User> users = userService.findAll();
return ResponseEntity.ok(ApiResponse.success(users));
}

// 根据 ID 查询用户
@GetMapping("/{id}")
public ResponseEntity<ApiResponse<User>> getUserById(@PathVariable Long id) {
User user = userService.findById(id);
if (user == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(ApiResponse.success(user));
}

// 创建用户
@PostMapping
public ResponseEntity<ApiResponse<User>> createUser(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(savedUser));
}

// 更新用户
@PutMapping("/{id}")
public ResponseEntity<ApiResponse<User>> updateUser(
@PathVariable Long id,
@Valid @RequestBody User user) {
User updatedUser = userService.update(id, user);
if (updatedUser == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(ApiResponse.success(updatedUser));
}

// 删除用户
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
boolean deleted = userService.delete(id);
if (!deleted) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.noContent().build();
}
}

统一响应格式

public class ApiResponse<T> {
private int code;
private String message;
private T data;

public static <T> ApiResponse<T> success(T data) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(200);
response.setMessage("success");
response.setData(data);
return response;
}

public static <T> ApiResponse<T> error(int code, String message) {
ApiResponse<T> response = new ApiResponse<>();
response.setCode(code);
response.setMessage(message);
return response;
}

// getters and setters
}

数据绑定与验证

数据绑定

Spring MVC 自动将请求参数绑定到 Controller 方法的参数。

@PostMapping("/user")
public String createUser(User user) {
// 自动将请求参数绑定到 User 对象
userService.save(user);
return "redirect:/user/list";
}

数据验证

使用 JSR-303 验证规范进行数据验证。

1. 添加依赖

<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.0.Final</version>
</dependency>

2. 实体类添加验证注解

public class User {
private Long id;

@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")
private String username;

@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;

@NotNull(message = "年龄不能为空")
@Min(value = 18, message = "年龄必须大于18岁")
@Max(value = 100, message = "年龄必须小于100岁")
private Integer age;

// getters and setters
}

3. Controller 中使用验证

@PostMapping("/user")
public String createUser(@Valid User user, BindingResult result, Model model) {
if (result.hasErrors()) {
// 验证失败,返回表单页面并显示错误信息
model.addAttribute("errors", result.getAllErrors());
return "user/form";
}

userService.save(user);
return "redirect:/user/list";
}

异常处理

@ExceptionHandler

处理 Controller 层的异常。

@Controller
public class UserController {

@GetMapping("/user/{id}")
public String getUser(@PathVariable Long id, Model model) {
User user = userService.findById(id);
if (user == null) {
throw new UserNotFoundException("用户不存在: " + id);
}
model.addAttribute("user", user);
return "user/detail";
}

@ExceptionHandler(UserNotFoundException.class)
public String handleUserNotFound(UserNotFoundException ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/404";
}
}

@ControllerAdvice

全局异常处理器。

@ControllerAdvice
public class GlobalExceptionHandler {

// 处理所有异常
@ExceptionHandler(Exception.class)
public String handleException(Exception ex, Model model) {
model.addAttribute("errorMessage", ex.getMessage());
return "error/500";
}

// 处理业务异常
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<ApiResponse<Void>> handleBusinessException(BusinessException ex) {
return ResponseEntity.badRequest()
.body(ApiResponse.error(ex.getCode(), ex.getMessage()));
}

// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidationException(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error -> {
errors.put(error.getField(), error.getDefaultMessage());
});
return ResponseEntity.badRequest()
.body(ApiResponse.error(400, "参数校验失败"));
}
}

拦截器

实现拦截器

public class LoginInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
// 在请求处理之前执行
String uri = request.getRequestURI();
if (uri.contains("/login") || uri.contains("/static/")) {
return true;
}

HttpSession session = request.getSession();
if (session.getAttribute("user") == null) {
response.sendRedirect("/login");
return false;
}
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response,
Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后执行,但在视图渲染之前
}

@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, Exception ex) throws Exception {
// 在视图渲染之后执行,可用于资源清理
}
}

配置拦截器

XML 配置

<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/login"/>
<mvc:exclude-mapping path="/static/**"/>
<bean class="com.example.interceptor.LoginInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>

Java 配置

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/login", "/static/**", "/error");
}
}

文件上传下载

文件上传

1. 配置文件上传解析器

<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="10485760"/> <!-- 10MB -->
<property name="defaultEncoding" value="UTF-8"/>
</bean>

2. Controller 处理文件上传

@Controller
@RequestMapping("/file")
public class FileController {

@PostMapping("/upload")
public String uploadFile(@RequestParam("file") MultipartFile file,
HttpServletRequest request) {
if (file.isEmpty()) {
throw new FileUploadException("请选择文件");
}

try {
String uploadPath = request.getServletContext().getRealPath("/uploads/");
File uploadDir = new File(uploadPath);
if (!uploadDir.exists()) {
uploadDir.mkdirs();
}

String fileName = System.currentTimeMillis() + "_" + file.getOriginalFilename();
File targetFile = new File(uploadPath + fileName);
file.transferTo(targetFile);

return "redirect:/file/list";
} catch (IOException e) {
throw new FileUploadException("文件上传失败", e);
}
}

@PostMapping("/uploadMultiple")
public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files) {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 处理每个文件
fileService.saveFile(file);
}
}
return "redirect:/file/list";
}
}

文件下载

@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> downloadFile(@PathVariable String fileName,
HttpServletRequest request) {
try {
String filePath = request.getServletContext().getRealPath("/uploads/") + fileName;
Path path = Paths.get(filePath);
Resource resource = new UrlResource(path.toUri());

if (resource.exists() || resource.isReadable()) {
String contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
if (contentType == null) {
contentType = "application/octet-stream";
}

return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"")
.body(resource);
}
} catch (MalformedURLException e) {
e.printStackTrace();
}

return ResponseEntity.notFound().build();
}

经典面试题

1. 请描述 Spring MVC 的工作流程

答案要点

  1. 用户发送请求到 DispatcherServlet
  2. DispatcherServlet 调用 HandlerMapping 查找处理器
  3. HandlerMapping 返回处理器执行链
  4. DispatcherServlet 调用 HandlerAdapter 执行处理器
  5. 处理器执行完成返回 ModelAndView
  6. DispatcherServlet 调用 ViewResolver 解析视图
  7. ViewResolver 返回具体的 View 对象
  8. DispatcherServlet 调用 View 渲染视图
  9. View 将渲染结果返回给客户端

2. Spring MVC 有哪些核心组件?它们的作用是什么?

答案要点

  • DispatcherServlet:前端控制器,统一请求入口
  • HandlerMapping:处理器映射器,建立 URL 与 Controller 的映射关系
  • HandlerAdapter:处理器适配器,适配并调用具体的处理器
  • Controller:处理器,处理具体请求
  • ViewResolver:视图解析器,解析逻辑视图名
  • View:视图,渲染模型数据

3. @RestController 和 @Controller 有什么区别?

答案要点

  • @Controller 是 Spring MVC 的注解,返回逻辑视图名
  • @RestController 是 @Controller + @ResponseBody 的组合注解
  • @RestController 直接将返回值写入 HTTP 响应体
  • @Controller 通常用于返回页面,@RestController 用于返回 JSON/XML 等数据

4. 如何实现 Spring MVC 的拦截器?

答案要点

  1. 实现 HandlerInterceptor 接口或继承 HandlerInterceptorAdapter
  2. 重写 preHandle、postHandle、afterCompletion 方法
  3. 通过 XML 或 Java 配置注册拦截器
  4. 可以配置拦截的路径和不拦截的路径

5. Spring MVC 如何处理异常?

答案要点

  1. @ExceptionHandler:处理 Controller 层的局部异常
  2. @ControllerAdvice:全局异常处理
  3. ResponseEntityExceptionHandler:处理 HTTP 相关异常
  4. 自定义异常处理器:实现 HandlerExceptionResolver 接口

6. 什么是 RESTful?如何在 Spring MVC 中实现 RESTful API?

答案要点RESTful 特点

  • 使用 HTTP 动词表示操作
  • 使用名词表示资源
  • 无状态
  • 统一的接口

Spring MVC 实现

  • 使用 @RestController 注解
  • 使用 @GetMapping、@PostMapping 等注解
  • 使用 @PathVariable 获取 URL 参数
  • 使用 @RequestBody 获取请求体
  • 返回适当的 HTTP 状态码

7. 如何防止表单重复提交?

答案要点

  1. 使用 Token 机制
  2. 前端禁用提交按钮
  3. 使用重定向模式
  4. 使用 Session 存储提交状态
// Token 机制示例
@Controller
public class FormController {

@GetMapping("/form")
public String showForm(HttpSession session, Model model) {
String token = UUID.randomUUID().toString();
session.setAttribute("formToken", token);
model.addAttribute("token", token);
return "form";
}

@PostMapping("/submit")
public String submitForm(@RequestParam("token") String token,
HttpSession session) {
String sessionToken = (String) session.getAttribute("formToken");
if (!token.equals(sessionToken)) {
throw new RuntimeException("重复提交");
}

// 移除 Token,防止重复提交
session.removeAttribute("formToken");

// 处理表单逻辑
return "success";
}
}

8. Spring MVC 的数据绑定是如何工作的?

答案要点

  1. 根据 ServletRequest 获取请求参数
  2. 使用 DataBinder 进行数据绑定
  3. 支持 JavaBean、集合、Map 等类型的绑定
  4. 支持自定义属性编辑器
  5. 支持数据验证

9. 如何在 Spring MVC 中实现国际化?

答案要点

  1. 配置 MessageSource
  2. 使用 LocaleResolver 解析区域信息
  3. 使用 LocaleChangeInterceptor 切换语言
  4. 在页面中使用 spring:message 标签
<!-- 配置 MessageSource -->
<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename" value="classpath:messages"/>
<property name="defaultEncoding" value="UTF-8"/>
</bean>

<!-- 配置 LocaleResolver -->
<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
<property name="defaultLocale" value="zh_CN"/>
</bean>

<!-- 配置拦截器 -->
<mvc:interceptors>
<bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
<property name="paramName" value="lang"/>
</bean>
</mvc:interceptors>

10. Spring MVC 和 Spring Boot 有什么关系?

答案要点

  • Spring Boot 是 Spring 生态的开发工具
  • Spring Boot 自动配置 Spring MVC
  • Spring Boot 简化了 Spring MVC 的配置
  • Spring Boot 提供了嵌入式 Web 容器
  • Spring Boot 推荐使用注解而非 XML 配置

实战技巧

1. 性能优化技巧

  • 静态资源缓存:配置合适的缓存策略
  • 视图缓存:对不经常变化的视图进行缓存
  • 数据压缩:启用 Gzip 压缩
  • 异步处理:使用 @Async 进行异步处理

2. 安全最佳实践

  • CSRF 防护:启用 CSRF 防护
  • XSS 防护:对用户输入进行转义
  • SQL 注入防护:使用预编译语句
  • 接口限流:防止恶意请求

3. 调试技巧

  • 日志记录:合理使用日志级别
  • 请求追踪:使用 MDC 进行请求链路追踪
  • 性能监控:集成 APM 工具
  • 单元测试:编写完整的单元测试

4. 常见问题解决

  • 404 错误:检查请求映射和视图配置
  • 400 错误:检查参数类型和格式
  • 500 错误:查看服务器日志,检查异常处理
  • 中文乱码:配置字符编码过滤器
<!-- 字符编码过滤器 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

总结

Spring MVC 作为经典的 Web 框架,在企业级应用开发中具有重要地位。通过深入理解其核心概念、工作流程和各种组件,能够帮助我们更好地构建高质量的 Web 应用程序。在实际开发中,要注重理论与实践相结合,多思考架构设计,关注性能和安全,这样才能真正掌握 Spring MVC 的精髓。

记住,Spring MVC 不仅仅是一个框架,更是一种设计思想的体现。掌握了它的核心原理,对于学习其他 Web 框架也会有很大帮助。