外观
SpringMVC 笔记
约 2549 字大约 9 分钟
2025-08-16
一、Spring MVC 简介
1.1 什么是 Spring MVC?
Spring Web MVC 是一种基于 Java 的实现了 Web MVC 设计模式的请求驱动类型的轻量级 Web 框架。它使用了 MVC 架构模式的思想,将 Web 层进行职责解耦,基于请求-响应模型,框架的目的就是帮助我们简化 Web 开发。
1.2 Spring MVC 核心特点
- 松耦合:各组件之间高度解耦,便于维护和测试
- 灵活的配置:支持 XML 和注解两种配置方式
- 强大的注解支持:简化控制器开发
- 丰富的视图技术:支持 JSP、Thymeleaf、Freemarker 等多种视图技术
- RESTful 支持:原生支持 RESTful 风格的 Web 服务开发
- 与 Spring 生态无缝集成:与 Spring Core、Spring Security 等无缝集成
二、Spring MVC 工作原理
2.1 核心组件
| 组件 | 说明 |
|---|---|
| DispatcherServlet | 前端控制器,负责接收所有请求并分发给相应的组件 |
| HandlerMapping | 处理器映射,根据请求 URL 找到对应的处理器(Controller) |
| Controller | 处理器,处理具体业务逻辑 |
| HandlerAdapter | 处理器适配器,调用 Controller 的方法 |
| ViewResolver | 视图解析器,将逻辑视图名解析为实际视图对象 |
| View | 视图,渲染模型数据并返回给客户端 |
2.2 请求处理流程
- 客户端发送 HTTP 请求到服务器
- 请求被
DispatcherServlet拦截 DispatcherServlet通过HandlerMapping查找合适的 ControllerDispatcherServlet通过HandlerAdapter调用 Controller 的方法- Controller 处理业务逻辑,返回
ModelAndView对象 DispatcherServlet将模型数据交给ViewResolver解析为视图- 视图渲染模型数据,生成最终响应
DispatcherServlet将响应返回给客户端

三、环境搭建与配置
3.1 依赖配置
Maven 配置:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.18</version>
</dependency>
<!-- 视图解析器(以Thymeleaf为例) -->
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>3.2 配置 DispatcherServlet
web.xml 配置:
<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>classpath:applicationContext.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>
</web-app>3.3 基于 Java 的配置(推荐)
public class WebInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{WebConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
@Configuration
@ComponentScan("com.example.controller")
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public ViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setOrder(1);
resolver.setCharacterEncoding("UTF-8");
return resolver;
}
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
}四、控制器与请求映射
4.1 控制器定义
// 传统 MVC 控制器
@Controller
public class UserController {
// 处理方法
}
// RESTful 控制器
@RestController
public class UserApiController {
// REST API 方法
}4.2 请求映射
@Controller
@RequestMapping("/users")
public class UserController {
// GET /users - 获取所有用户
@GetMapping
public String getAllUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "users"; // 视图名称
}
// GET /users/{id} - 获取指定用户
@GetMapping("/{id}")
public String getUserById(@PathVariable Long id, Model model) {
model.addAttribute("user", userService.findById(id));
return "user-detail";
}
// POST /users - 创建新用户
@PostMapping
public String createUser(@Valid User user, BindingResult result, RedirectAttributes redirectAttrs) {
if (result.hasErrors()) {
return "user-form";
}
userService.save(user);
redirectAttrs.addFlashAttribute("message", "用户创建成功");
return "redirect:/users";
}
// PUT /users/{id} - 更新用户
@PutMapping("/{id}")
@ResponseBody
public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User userDetails) {
// 更新逻辑
}
// DELETE /users/{id} - 删除用户
@DeleteMapping("/{id}")
public String deleteUser(@PathVariable Long id) {
userService.deleteById(id);
return "redirect:/users";
}
}4.3 常用请求映射注解
| 注解 | 说明 | 示例 |
|---|---|---|
@RequestMapping | 通用请求映射 | @RequestMapping("/users") |
@GetMapping | GET 请求映射 | @GetMapping("/{id}") |
@PostMapping | POST 请求映射 | @PostMapping |
@PutMapping | PUT 请求映射 | @PutMapping("/{id}") |
@DeleteMapping | DELETE 请求映射 | @DeleteMapping("/{id}") |
@PatchMapping | PATCH 请求映射 | @PatchMapping("/{id}") |
五、请求参数处理
5.1 路径变量
// GET /users/123
@GetMapping("/{id}")
public String getUserById(@PathVariable Long id) {
// 使用 id
}
// GET /users/123/orders/456
@GetMapping("/{userId}/orders/{orderId}")
public String getOrder(@PathVariable Long userId, @PathVariable Long orderId) {
// 使用 userId 和 orderId
}5.2 请求参数
// GET /search?keyword=spring&page=1&size=10
@GetMapping("/search")
public String searchUsers(
@RequestParam String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
// 使用 keyword, page, size
}
// 可选参数
@GetMapping("/advanced-search")
public String advancedSearch(
@RequestParam(required = false) String name,
@RequestParam(required = false) Integer age) {
// name 和 age 是可选的
}5.3 请求体处理
// 处理 JSON 请求体
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) {
// 使用 loginRequest.getUsername() 和 loginRequest.getPassword()
}
// LoginRequest 类
public class LoginRequest {
private String username;
private String password;
// getters and setters
}5.4 表单数据处理
// 处理表单提交
@PostMapping("/register")
public String registerUser(@Valid UserRegistrationForm form, BindingResult result, Model model) {
if (result.hasErrors()) {
return "register-form";
}
// 处理表单数据
userService.register(form);
return "redirect:/login";
}
// 表单对象
public class UserRegistrationForm {
@NotBlank
private String username;
@Email
private String email;
@Size(min = 6)
private String password;
// getters and setters
}5.5 模型属性
// 在控制器方法执行前自动添加到模型
@ModelAttribute("countries")
public List<String> getCountries() {
return Arrays.asList("中国", "美国", "日本", "韩国");
}
// 将请求参数绑定到对象
@PostMapping("/profile")
public String updateProfile(@ModelAttribute UserProfile profile) {
// profile 对象已自动绑定请求参数
userService.updateProfile(profile);
return "redirect:/profile";
}六、响应处理
6.1 返回视图
// 返回视图名称
@GetMapping("/users")
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "users/list"; // 逻辑视图名
}
// 重定向
@PostMapping("/users")
public String createUser(User user) {
userService.save(user);
return "redirect:/users"; // 重定向到 GET /users
}
// 请求转发
@GetMapping("/home")
public String home() {
return "forward:/index"; // 转发到 /index
}6.2 返回 JSON(RESTful)
@RestController
@RequestMapping("/api/users")
public class UserApiController {
@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
URI location = ServletUriComponentsBuilder
.fromCurrentRequest()
.path("/{id}")
.buildAndExpand(savedUser.getId())
.toUri();
return ResponseEntity.created(location).body(savedUser);
}
}6.3 响应状态码
// 自定义响应状态码
@PostMapping
public ResponseEntity<User> createUser(@Valid @RequestBody User user) {
User savedUser = userService.save(user);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(savedUser);
}
// 404 响应
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
return userService.findById(id)
.map(ResponseEntity::ok)
.orElse(ResponseEntity.notFound().build());
}
// 204 No Content
@DeleteMapping("/{id}")
public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
userService.deleteById(id);
return ResponseEntity.noContent().build();
}七、拦截器
7.1 拦截器实现
@Component
public class LoggingInterceptor implements HandlerInterceptor {
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
logger.info("请求开始: {} {}", request.getMethod(), request.getRequestURI());
return true; // 继续处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
logger.info("请求处理完成: {} {}", request.getMethod(), request.getRequestURI());
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
logger.info("请求完成: {} {}", request.getMethod(), request.getRequestURI());
if (ex != null) {
logger.error("请求处理异常", ex);
}
}
}
// 权限拦截器
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
HttpSession session = request.getSession();
if (session.getAttribute("user") == null) {
response.sendRedirect("/login");
return false;
}
return true;
}
}7.2 拦截器配置
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoggingInterceptor loggingInterceptor;
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loggingInterceptor)
.addPathPatterns("/**"); // 拦截所有请求
registry.addInterceptor(authInterceptor)
.addPathPatterns("/admin/**", "/user/**") // 只拦截特定路径
.excludePathPatterns("/login", "/public/**"); // 排除特定路径
}
}八、异常处理
8.1 全局异常处理
@ControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
// 处理资源未找到异常
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleResourceNotFound(ResourceNotFoundException ex) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
"Resource Not Found",
ex.getMessage()
);
return new ResponseEntity<>(error, HttpStatus.NOT_FOUND);
}
// 处理参数验证异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<Map<String, String>> handleValidationExceptions(
MethodArgumentNotValidException ex) {
Map<String, String> errors = new HashMap<>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
// 处理所有其他异常
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleGlobalException(Exception ex) {
logger.error("Unexpected error", ex);
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"Internal Server Error",
"An unexpected error occurred"
);
return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
}
// 错误响应类
@Data
@AllArgsConstructor
public static class ErrorResponse {
private int status;
private String title;
private String message;
}
}8.2 自定义错误页面
// 配置自定义错误页面
@Configuration
public class ErrorConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/404").setViewName("error/404");
registry.addViewController("/500").setViewName("error/500");
}
}
// 在控制器中处理错误
@Controller
public class ErrorController {
@RequestMapping("/error")
public String handleError(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == 404) {
return "error/404";
} else if (statusCode == 500) {
return "error/500";
}
return "error/generic";
}
}九、视图技术集成
9.1 Thymeleaf 集成
依赖配置:
<dependency>
<groupId>org.thymeleaf</groupId>
<artifactId>thymeleaf-spring5</artifactId>
<version>3.0.15.RELEASE</version>
</dependency>Thymeleaf 配置:
@Bean
public SpringResourceTemplateResolver templateResolver() {
SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".html");
resolver.setTemplateMode(TemplateMode.HTML);
resolver.setCharacterEncoding("UTF-8");
resolver.setCacheable(false); // 开发环境禁用缓存
return resolver;
}
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine engine = new SpringTemplateEngine();
engine.setTemplateResolver(templateResolver());
return engine;
}
@Bean
public ThymeleafViewResolver viewResolver() {
ThymeleafViewResolver resolver = new ThymeleafViewResolver();
resolver.setTemplateEngine(templateEngine());
resolver.setCharacterEncoding("UTF-8");
return resolver;
}Thymeleaf 模板示例:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>用户列表</title>
</head>
<body>
<h1>用户列表</h1>
<table border="1">
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
</tr>
<tr th:each="user : ${users}">
<td th:text="${user.id}"></td>
<td th:text="${user.name}"></td>
<td th:text="${user.email}"></td>
</tr>
</table>
<p th:if="${empty users}">没有找到用户</p>
<a th:href="@{/users/new}">创建新用户</a>
</body>
</html>9.2 静态资源处理
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 配置静态资源路径
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
// 配置 favicon
registry.addResourceHandler("/favicon.ico")
.addResourceLocations("classpath:/static/favicon.ico");
// 配置 WebJars 资源
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}十、最佳实践
10.1 项目结构规范
src/main/java
└── com
└── example
├── Application.java # 主启动类
├── config # 配置类
├── controller # 控制器
│ ├── UserController.java
│ └── ApiController.java
├── service # 服务层
│ ├── UserService.java
│ └── impl # 服务实现
├── repository # 数据访问层
├── model # 模型类
│ ├── entity # 实体类
│ ├── dto # 数据传输对象
│ └── form # 表单对象
└── exception # 自定义异常10.2 RESTful API 设计规范
资源命名:
- 使用名词复数表示资源集合
- 使用连字符
-代替下划线_ - 保持小写
/api/users /api/ordersHTTP 方法与资源操作:
GET /api/users # 获取用户列表 POST /api/users # 创建新用户 GET /api/users/{id} # 获取指定用户 PUT /api/users/{id} # 全量更新用户 PATCH /api/users/{id} # 部分更新用户 DELETE /api/users/{id} # 删除用户状态码:
- 200 OK:成功
- 201 Created:资源创建成功
- 204 No Content:操作成功但无内容返回
- 400 Bad Request:客户端错误
- 401 Unauthorized:未认证
- 403 Forbidden:无权限
- 404 Not Found:资源不存在
- 500 Internal Server Error:服务器错误
10.3 参数验证
// 实体类验证
public class User {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3-50之间")
private String username;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{6,}$",
message = "密码至少6位,包含字母和数字")
private String password;
// 其他字段...
}
// 控制器中使用
@PostMapping("/register")
public ResponseEntity<?> registerUser(@Valid @RequestBody User user, BindingResult result) {
if (result.hasErrors()) {
Map<String, String> errors = new HashMap<>();
result.getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
return ResponseEntity.badRequest().body(errors);
}
// 处理注册逻辑
}10.4 跨域配置
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:3000", "https://example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}
// 或者使用 @CrossOrigin 注解
@RestController
@RequestMapping("/api/users")
@CrossOrigin(origins = "http://localhost:3000")
public class UserApiController {
// REST API 方法
}