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 应用程序。
核心优势
- 角色分离清晰:Model、View、Controller 各司其职
- 灵活性强:支持多种视图技术(JSP、Thymeleaf、FreeMarker等)
- 与 Spring 生态无缝集成:充分利用 Spring 的 IoC 和 AOP 特性
- 测试友好:易于进行单元测试和集成测试
核心架构与工作流程
工作流程图
用户请求 → DispatcherServlet → HandlerMapping → Controller → Model
↑ ↓
← ViewResolver ← View ← ModelAndView ← Service ← Repository ← DAO
详细执行步骤
- 用户发送请求:用户通过浏览器发送 HTTP 请求
- DispatcherServlet 接收请求:前端控制器接收所有请求并进行分发
- HandlerMapping 查找处理器:根据请求 URL 找到对应的 Controller 方法
- HandlerAdapter 适配处理器:适配并调用具体的 Controller 方法
- Controller 处理请求:执行业务逻辑,返回 ModelAndView
- ViewResolver 解析视图:根据视图名称解析具体的视图对象
- View 渲染视图:将模型数据渲染到视图中
- 返回响应:将渲染结果返回给用户
核心组件详解
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:用于 JSPThymeleafViewResolver:用于 ThymeleafFreeMarkerViewResolver:用于 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 设计原则
- 使用 HTTP 动词表示操作:GET(查询)、POST(创建)、PUT(更新)、DELETE(删除)
- 使用名词表示资源
- 使用合适的 HTTP 状态码
- 统一的响应格式
实际案例
@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 的工作流程
答案要点:
- 用户发送请求到 DispatcherServlet
- DispatcherServlet 调用 HandlerMapping 查找处理器
- HandlerMapping 返回处理器执行链
- DispatcherServlet 调用 HandlerAdapter 执行处理器
- 处理器执行完成返回 ModelAndView
- DispatcherServlet 调用 ViewResolver 解析视图
- ViewResolver 返回具体的 View 对象
- DispatcherServlet 调用 View 渲染视图
- 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 的拦截器?
答案要点:
- 实现 HandlerInterceptor 接口或继承 HandlerInterceptorAdapter
- 重写 preHandle、postHandle、afterCompletion 方法
- 通过 XML 或 Java 配置注册拦截器
- 可以配置拦截的路径和不拦截的路径
5. Spring MVC 如何处理异常?
答案要点:
- @ExceptionHandler:处理 Controller 层的局部异常
- @ControllerAdvice:全局异常处理
- ResponseEntityExceptionHandler:处理 HTTP 相关异常
- 自定义异常处理器:实现 HandlerExceptionResolver 接口
6. 什么是 RESTful?如何在 Spring MVC 中实现 RESTful API?
答案要点: RESTful 特点:
- 使用 HTTP 动词表示操作
- 使用名词表示资源
- 无状态
- 统一的接口
Spring MVC 实现:
- 使用 @RestController 注解
- 使用 @GetMapping、@PostMapping 等注解
- 使用 @PathVariable 获取 URL 参数
- 使用 @RequestBody 获取请求体
- 返回适当的 HTTP 状态码
7. 如何防止表单重复提交?
答案要点:
- 使用 Token 机制
- 前端禁用提交按钮
- 使用重定向模式
- 使用 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 的数据绑定是如何工作的?
答案要点:
- 根据 ServletRequest 获取请求参数
- 使用 DataBinder 进行数据绑定
- 支持 JavaBean、集合、Map 等类型的绑定
- 支持自定义属性编辑器
- 支持数据验证
9. 如何在 Spring MVC 中实现国际化?
答案要点:
- 配置 MessageSource
- 使用 LocaleResolver 解析区域信息
- 使用 LocaleChangeInterceptor 切换语言
- 在页面中使用 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 框架也会有很大帮助。