Spring MVC 概述 什么是 Spring MVC Spring MVC 是 Spring 框架的一部分, 用于构建 Web 应用程序。 它遵循 Model-View-Controller( MVC) 设计模式, 将业务逻辑、 数据和展示分离。
核心功能
请求映射: 将 URL 映射到控制器方法
数据绑定: 自动将请求参数绑定到 Java 对象
视图解析: 支持多种视图技术( JSP、 Thymeleaf、 FreeMarker)
拦截器: 提供请求预处理和后处理
主要优势
清晰的角色划分: 控制器、 验证器、 命令对象等
强大的配置: 支持 XML 和注解配置
可适配的处理器映射和视图解析
灵活的模型传递
Spring MVC 的核心概念 MVC 架构
组件
职责
示例
Model( 模型)
封装业务数据和逻辑
Entity、 Service、 DAO
View( 视图)
展示数据给用户
JSP、 Thymeleaf、 JSON
Controller( 控制器)
处理用户请求, 协调 Model 和 View
@Controller 类
核心组件
DispatcherServlet : 前端控制器, 所有请求的入口
HandlerMapping : 处理器映射, 找到对应的 Controller
Controller : 处理请求的控制器
ModelAndView : 包含模型数据和视图信息
ViewResolver : 视图解析器, 解析视图名称
View : 具体的视图实现
Spring MVC 的工作原理 请求处理流程 1 2 3 4 5 6 7 1. 用户发送请求到 DispatcherServlet 2. DispatcherServlet 查询 HandlerMapping 找到对应的 Controller 3. DispatcherServlet 调用 Controller 处理请求 4. Controller 执行业务逻辑, 返回 ModelAndView 5. DispatcherServlet 通过 ViewResolver 解析视图 6. View 渲染模型数据 7. DispatcherServlet 将响应返回给用户
组件交互 1 2 3 4 5 6 7 客户端 → DispatcherServlet → HandlerMapping → Controller ↓ ModelAndView ↓ ViewResolver → View ↓ 响应客户端
环境搭建 添加依赖 Maven 依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 5.3.20</version > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > javax.servlet-api</artifactId > <version > 4.0.1</version > <scope > provided</scope > </dependency > <dependency > <groupId > javax.servlet.jsp</groupId > <artifactId > javax.servlet.jsp-api</artifactId > <version > 2.3.3</version > <scope > provided</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > jstl</artifactId > <version > 1.2</version > </dependency >
配置 Spring MVC web.xml 配置( 传统方式) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <?xml version="1.0" encoding="UTF-8" ?> <web-app xmlns ="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version ="4.0" > <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-mvc.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 > <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 > </web-app >
spring-mvc.xml 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <?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 /> <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 Config 配置( 推荐) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.example.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.ViewResolver;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.view.InternalResourceViewResolver;@Configuration @EnableWebMvc @ComponentScan("com.example.controller") public class WebConfig implements WebMvcConfigurer { @Bean public ViewResolver 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/" ); } }
Servlet 初始化器( 替代 web.xml) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.example.config;import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class []{AppConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class []{WebConfig.class}; } @Override protected String[] getServletMappings() { return new String []{"/" }; } }
创建示例项目 实体类 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 package com.example.entity;public class User { private Integer id; private String username; private String email; public User () {} public User (Integer id, String username, String email) { this .id = id; this .username = username; this .email = email; } public Integer getId () { return id; } public void setId (Integer id) { this .id = id; } public String getUsername () { return username; } public void setUsername (String username) { this .username = username; } public String getEmail () { return email; } public void setEmail (String email) { this .email = email; } }
Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.example.controller;import com.example.entity.User;import org.springframework.stereotype.Controller;import org.springframework.ui.Model;import org.springframework.web.bind.annotation.*;import java.util.ArrayList;import java.util.List;@Controller @RequestMapping("/users") public class UserController { private List<User> users = new ArrayList <>(); public UserController () { users.add(new User (1 , "张三" , "zhangsan@example.com" )); users.add(new User (2 , "李四" , "lisi@example.com" )); } @GetMapping public String list (Model model) { model.addAttribute("users" , users); return "user/list" ; } @GetMapping("/{id}") public String detail (@PathVariable Integer id, Model model) { User user = users.stream() .filter(u -> u.getId().equals(id)) .findFirst() .orElse(null ); model.addAttribute("user" , user); return "user/detail" ; } @GetMapping("/add") public String showAddForm (Model model) { model.addAttribute("user" , new User ()); return "user/form" ; } @PostMapping public String add (User user) { user.setId(users.size() + 1 ); users.add(user); return "redirect:/users" ; } @GetMapping("/delete/{id}") public String delete (@PathVariable Integer id) { users.removeIf(u -> u.getId().equals(id)); return "redirect:/users" ; } }
JSP 视图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <!-- WEB-INF/views/user/list.jsp --> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>用户列表</title> </head> <body> <h1>用户列表</h1> <a href="${pageContext.request.contextPath}/users/add" >新增用户</a> <table border="1" > <tr> <th>ID</th> <th>用户名</th> <th>邮箱</th> <th>操作</th> </tr> <c:forEach items="${users}" var ="user" > <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.email}</td> <td> <a href="${pageContext.request.contextPath}/users/${user.id}" >详情</a> <a href="${pageContext.request.contextPath}/users/delete/${user.id}" >删除</a> </td> </tr> </c:forEach> </table> </body> </html>
Controller 基本用法 @Controller 和 @RestController 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Controller public class PageController { @GetMapping("/home") public String home () { return "home" ; } } @RestController @RequestMapping("/api/users") public class UserApiController { @GetMapping public List<User> list () { return userService.findAll(); } }
请求映射 @RequestMapping 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Controller @RequestMapping("/users") public class UserController { @RequestMapping(value = "/list", method = RequestMethod.GET) public String list () { return "user/list" ; } @RequestMapping(value = "/add", method = RequestMethod.POST) public String add (User user) { userService.save(user); return "redirect:/users/list" ; } }
简化注解 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @GetMapping @PostMapping @PutMapping @DeleteMapping @PatchMapping @GetMapping("/list") public String list () { return "user/list" ; } @PostMapping("/add") public String add (User user) { userService.save(user); return "redirect:/users/list" ; }
参数绑定 路径变量 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @GetMapping("/users/{id}") public String detail (@PathVariable Integer id, Model model) { User user = userService.findById(id); model.addAttribute("user" , user); return "user/detail" ; } @GetMapping("/users/{userId}/orders/{orderId}") public String orderDetail ( @PathVariable Integer userId, @PathVariable Integer orderId ) { }
请求参数 1 2 3 4 5 6 7 8 9 10 11 12 @GetMapping("/search") public String search ( @RequestParam String keyword, @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size, @RequestParam(required = false) String sort, Model model ) { List<User> users = userService.search(keyword, page, size, sort); model.addAttribute("users" , users); return "user/list" ; }
请求体 1 2 3 4 5 @PostMapping("/add") public String add (@RequestBody User user) { userService.save(user); return "redirect:/users" ; }
表单对象 1 2 3 4 5 6 7 8 @PostMapping("/register") public String register (User user, BindingResult result) { if (result.hasErrors()) { return "register" ; } userService.save(user); return "redirect:/login" ; }
其他参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @GetMapping("/info") public String info (@RequestHeader("User-Agent") String userAgent) { } @GetMapping("/cookie") public String cookie (@CookieValue("sessionId") String sessionId) { } @GetMapping("/request") public String request (HttpServletRequest request) { String ip = request.getRemoteAddr(); } @GetMapping("/session") public String session (HttpSession session) { session.setAttribute("user" , user); }
返回值 视图名称 1 2 3 4 @GetMapping("/home") public String home () { return "home" ; }
ModelAndView 1 2 3 4 5 6 7 @GetMapping("/users") public ModelAndView list () { ModelAndView mav = new ModelAndView (); mav.setViewName("user/list" ); mav.addObject("users" , userService.findAll()); return mav; }
JSON 数据 1 2 3 4 5 6 7 8 9 10 11 12 13 @RestController public class UserApiController { @GetMapping("/users") public List<User> list () { return userService.findAll(); } @GetMapping("/users/{id}") public User detail (@PathVariable Integer id) { return userService.findById(id); } }
重定向 1 2 3 4 5 @PostMapping("/add") public String add (User user) { userService.save(user); return "redirect:/users/list" ; }
转发 1 2 3 4 @GetMapping("/forward") public String forward () { return "forward:/users/list" ; }
数据验证 Hibernate Validator 添加依赖 1 2 3 4 5 <dependency > <groupId > org.hibernate.validator</groupId > <artifactId > hibernate-validator</artifactId > <version > 6.2.0.Final</version > </dependency >
实体类验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class User { @NotBlank(message = "用户名不能为空") @Size(min = 2, max = 20, message = "用户名长度必须在2-20之间") private String username; @Email(message = "邮箱格式不正确") private String email; @Min(value = 18, message = "年龄必须大于等于18") @Max(value = 100, message = "年龄必须小于等于100") private Integer age; @NotNull(message = "性别不能为空") private String gender; }
Controller 验证 1 2 3 4 5 6 7 8 9 10 @PostMapping("/add") public String add (@Valid User user, BindingResult result, Model model) { if (result.hasErrors()) { model.addAttribute("errors" , result.getAllErrors()); return "user/form" ; } userService.save(user); return "redirect:/users" ; }
常用验证注解
注解
说明
@NotNull
不能为 null
@NotBlank
不能为 null 或空字符串
@NotEmpty
不能为 null 或空集合
@Size
字符串长度或集合大小
@Min / @Max
最小值 / 最大值
@Email
邮箱格式
@Pattern
正则表达式匹配
@Past / @Future
过去时间 / 未来时间
拦截器 自定义拦截器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package com.example.interceptor;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import javax.servlet.http.HttpSession;public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("user" ); if (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 配置 1 2 3 4 5 6 7 <mvc:interceptors > <mvc:interceptor > <mvc:mapping path ="/admin/**" /> <mvc:exclude-mapping path ="/admin/login" /> <bean class ="com.example.interceptor.AuthInterceptor" /> </mvc:interceptor > </mvc:interceptors >
Java Config 配置 1 2 3 4 5 6 7 8 9 10 @Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(new AuthInterceptor ()) .addPathPatterns("/admin/**" ) .excludePathPatterns("/admin/login" ); } }
文件上传 配置文件上传解析器 XML 配置 1 2 3 4 5 <bean id ="multipartResolver" class ="org.springframework.web.multipart.commons.CommonsMultipartResolver" > <property name ="maxUploadSize" value ="10485760" /> <property name ="defaultEncoding" value ="UTF-8" /> </bean >
添加依赖 1 2 3 4 5 <dependency > <groupId > commons-fileupload</groupId > <artifactId > commons-fileupload</artifactId > <version > 1.4</version > </dependency >
处理文件上传 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @PostMapping("/upload") public String upload ( @RequestParam("file") MultipartFile file, @RequestParam String description, Model model ) { if (!file.isEmpty()) { try { String fileName = file.getOriginalFilename(); String uploadDir = "/uploads/" ; File dest = new File (uploadDir + fileName); file.transferTo(dest); model.addAttribute("message" , "上传成功" ); } catch (IOException e) { model.addAttribute("message" , "上传失败: " + e.getMessage()); } } return "upload/result" ; }
多文件上传 1 2 3 4 5 6 7 8 9 @PostMapping("/uploadMultiple") public String uploadMultiple (@RequestParam("files") MultipartFile[] files) { for (MultipartFile file : files) { if (!file.isEmpty()) { } } return "redirect:/upload" ; }
异常处理 局部异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Controller public class UserController { @ExceptionHandler(ArithmeticException.class) public String handleArithmeticException (Exception e, Model model) { model.addAttribute("error" , e.getMessage()); return "error/arithmetic" ; } @ExceptionHandler(Exception.class) public String handleException (Exception e, Model model) { model.addAttribute("error" , "系统异常" ); return "error/general" ; } }
全局异常处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity<Map<String, Object>> handleBusinessException (BusinessException e) { Map<String, Object> result = new HashMap <>(); result.put("code" , e.getCode()); result.put("message" , e.getMessage()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result); } @ExceptionHandler(Exception.class) public ResponseEntity<Map<String, Object>> handleException (Exception e) { Map<String, Object> result = new HashMap <>(); result.put("code" , 500 ); result.put("message" , "系统异常" ); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result); } }
RESTful API RESTful 设计
HTTP 方法
URL
说明
GET
/api/users
获取用户列表
GET
/api/users/1
获取 ID 为 1 的用户
POST
/api/users
创建新用户
PUT
/api/users/1
更新 ID 为 1 的用户
DELETE
/api/users/1
删除 ID 为 1 的用户
RESTful Controller 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 @RestController @RequestMapping("/api/users") public class UserApiController { @Autowired private UserService userService; @GetMapping public ResponseEntity<List<User>> list () { List<User> users = userService.findAll(); return ResponseEntity.ok(users); } @GetMapping("/{id}") public ResponseEntity<User> detail (@PathVariable Integer id) { User user = userService.findById(id); if (user == null ) { return ResponseEntity.notFound().build(); } return ResponseEntity.ok(user); } @PostMapping public ResponseEntity<User> create (@RequestBody User user) { userService.save(user); return ResponseEntity.status(HttpStatus.CREATED).body(user); } @PutMapping("/{id}") public ResponseEntity<Void> update (@PathVariable Integer id, @RequestBody User user) { user.setId(id); userService.update(user); return ResponseEntity.noContent().build(); } @DeleteMapping("/{id}") public ResponseEntity<Void> delete (@PathVariable Integer id) { userService.delete(id); return ResponseEntity.noContent().build(); } }
最佳实践 项目结构规范 1 2 3 4 5 6 7 8 9 10 11 src/main/java/com/example/ ├── controller/ # 控制器 ├── service/ # 业务层 │ └── impl/ # 业务实现 ├── dao/ # 数据访问层 ├── entity/ # 实体类 ├── dto/ # 数据传输对象 ├── vo/ # 视图对象 ├── interceptor/ # 拦截器 ├── exception/ # 异常类 └── config/ # 配置类
统一返回结果 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Data @AllArgsConstructor @NoArgsConstructor public class Result <T> { private Integer code; private String message; private T data; public static <T> Result<T> success (T data) { return new Result <>(200 , "success" , data); } public static <T> Result<T> error (Integer code, String message) { return new Result <>(code, message, null ); } }
跨域配置 1 2 3 4 5 6 7 8 9 10 11 12 @Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings (CorsRegistry registry) { registry.addMapping("/**" ) .allowedOrigins("*" ) .allowedMethods("GET" , "POST" , "PUT" , "DELETE" , "OPTIONS" ) .allowedHeaders("*" ) .maxAge(3600 ); } }
常见问题 中文乱码 1 2 3 4 5 6 7 8 9 10 11 12 13 问题: 请求参数或响应内容中文乱码 解决方案: 1. 配置 CharacterEncodingFilter 2. 在 web.xml 中添加: <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> </filter>
404 Not Found 1 2 3 4 5 6 7 8 9 10 11 问题: 访问 URL 返回 404 错误原因: 1. 请求路径错误 2. Controller 未正确配置 3. 包扫描范围不对 解决方案: 1. 检查 @RequestMapping 路径 2. 确认 Controller 类上有 @Controller 注解 3. 确保 Controller 在扫描范围内
报错处理 💗💗 Spring MVC 报错: No mapping found 1 2 3 4 5 6 7 8 9 10 11 12 错误信息: No mapping found for HTTP request with URI [/xxx] in DispatcherServlet 错误原因: 1. URL 路径不匹配 2. Controller 未被扫描到 3. 缺少 @RequestMapping 注解 解决方案: 1. 检查 URL 路径是否正确 2. 确认 @ComponentScan 包含 Controller 所在包 3. 添加正确的请求映射注解
1 2 3 4 5 6 7 8 9 10 错误信息: HTTP Status 415 – Unsupported Media Type 错误原因: 1. Content-Type 不匹配 2. 缺少 Jackson 依赖 解决方案: 1. 设置正确的 Content-Type: application/json 2. 添加 jackson-databind 依赖
学习资源
视频
尚硅谷 SpringMVC 教程: https://www.bilibili.com/video/BV1Ry4y1574R
官方文档
Spring MVC 官方文档: https://docs.spring.io/spring-framework/docs/current/reference/html/web.html
书籍
《 Spring MVC 学习指南》 : Paul Deck 著
《 精通 Spring MVC 4》 : Geoffroy Warin 著
教程
Spring MVC 入门教程: https://www.runoob.com/w3cnote/spring-mvc-tutorial.html
Baeldung Spring MVC 教程: https://www.baeldung.com/category/spring-mvc/
社区
Stack Overflow Spring MVC 标签: https://stackoverflow.com/questions/tagged/spring-mvc