Spring MVC 详解
Spring MVC是Spring框架的核心Web模块,基于MVC(Model-View-Controller)设计模式构建,提供了灵活、强大的Web应用开发解决方案。它是构建企业级Web应用的标准选择。
Spring MVC = 松耦合架构 + 灵活配置 + 强大扩展 + 企业级支持
- 🏗️ 松耦合架构:基于MVC设计模式,实现各层分离
- ⚙️ 灵活配置:多种配置方式,适应不同需求
- 🔌 强大扩展:丰富的拦截器和视图技术
- 🔒 企业级支持:认证、安全、性能优化等特性
1. Spring MVC基础概念
1.1 什么是Spring MVC?
Spring MVC是一个基于Java的Web框架,实现了MVC设计模式,用于构建Web应用程序。它提供了完整的Web开发解决方案,包括请求处理、视图渲染、数据绑定等功能。
MVC架构模式
- Model(模型)
- View(视图)
- Controller(控制器)
1// Model(模型)- 数据和业务逻辑2@Entity3public class User {4 @Id5 @GeneratedValue(strategy = GenerationType.IDENTITY)6 private Long id;7 private String name;8 private String email;9 10 // getter和setter方法11 public Long getId() { return id; }12 public void setId(Long id) { this.id = id; }13 public String getName() { return name; }14 public void setName(String name) { this.name = name; }15 public String getEmail() { return email; }16 public void setEmail(String email) { this.email = email; }17}1<!-- View(视图)- 用户界面 -->2<!DOCTYPE html>3<html>4<head>5 <title>用户列表</title>6</head>7<body>8 <h1>用户列表</h1>9 <table>10 <tr>11 <th>ID</th>12 <th>姓名</th>13 <th>邮箱</th>14 </tr>15 <tr th:each="user : ${users}">16 <td th:text="${user.id}"></td>17 <td th:text="${user.name}"></td>18 <td th:text="${user.email}"></td>19 </tr>20 </table>21</body>22</html>1// Controller(控制器)- 处理请求和响应2@Controller3@RequestMapping("/users")4public class UserController {5 @Autowired6 private UserService userService;7 8 @GetMapping9 public String listUsers(Model model) {10 List<User> users = userService.findAll();11 model.addAttribute("users", users);12 return "user/list";13 }14}1.2 Spring MVC核心组件
Spring MVC框架由多个核心组件组成,它们协同工作处理web请求并生成响应。
| 组件 | 作用 | 实现类 |
|---|---|---|
| DispatcherServlet | 前端控制器,统一处理请求 | org.springframework.web.servlet.DispatcherServlet |
| HandlerMapping | 处理器映射,根据URL找到Handler | RequestMappingHandlerMapping |
| HandlerAdapter | 处理器适配器,执行Handler | RequestMappingHandlerAdapter |
| ViewResolver | 视图解析器,解析视图名称 | InternalResourceViewResolver |
| Handler | 处理器,执行业务逻辑 | @Controller注解的类 |
| View | 视图,渲染响应 | JSP、Thymeleaf等 |
Spring MVC通过这些核心组件的协作,实现了请求的统一处理和响应的统一返回,提供了松耦合的Web开发架构。
2. Spring MVC请求处理流程
2.1 请求处理流程详解
Spring MVC的请求处理流程是一个完整的责任链模式实现:
请求处理流程示例代码
1public class RequestProcessingFlow {2 3 public void processRequest(HttpServletRequest request, HttpServletResponse response) {4 try {5 // 1. 请求进入DispatcherServlet6 DispatcherServlet dispatcher = new DispatcherServlet();7 8 // 2. 获取HandlerMapping9 HandlerMapping handlerMapping = getHandlerMapping();10 11 // 3. 根据请求URL找到对应的Handler12 Object handler = handlerMapping.getHandler(request);13 14 // 4. 获取HandlerAdapter15 HandlerAdapter handlerAdapter = getHandlerAdapter(handler);16 17 // 5. 执行Handler方法18 ModelAndView mv = handlerAdapter.handle(request, response, handler);19 20 // 6. 处理视图21 if (mv != null) {22 processView(request, response, mv);23 }24 25 } catch (Exception e) {26 // 异常处理27 handleException(request, response, e);28 }29 }30 31 private HandlerMapping getHandlerMapping() {32 return new RequestMappingHandlerMapping();33 }34 35 private HandlerAdapter getHandlerAdapter(Object handler) {36 return new RequestMappingHandlerAdapter();37 }38 39 private void processView(HttpServletRequest request, HttpServletResponse response, ModelAndView mv) {40 ViewResolver viewResolver = new InternalResourceViewResolver();41 View view = viewResolver.resolveViewName(mv.getViewName(), Locale.getDefault());42 view.render(mv.getModel(), request, response);43 }44}详细流程说明
- 请求接收:客户端发送HTTP请求到DispatcherServlet
- Handler映射:通过HandlerMapping找到对应的Handler
- Handler适配:通过HandlerAdapter适配并执行Handler
- 业务处理:Handler执行业务逻辑,返回ModelAndView
- 视图解析:通过ViewResolver解析视图名称
- 视图渲染:渲染视图并返回响应
2.2 核心接口详解
- HandlerMapping
- HandlerAdapter
- ViewResolver
1// HandlerMapping接口2public interface HandlerMapping {3 Object getHandler(HttpServletRequest request) throws Exception;4}56// 实现类7public class RequestMappingHandlerMapping implements HandlerMapping {8 @Override9 public Object getHandler(HttpServletRequest request) throws Exception {10 // 根据请求URL找到对应的Handler11 String uri = request.getRequestURI();12 // 查找映射关系13 return findHandler(uri);14 }15}1// HandlerAdapter接口2public interface HandlerAdapter {3 boolean supports(Object handler);4 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;5}67// 实现类8public class RequestMappingHandlerAdapter implements HandlerAdapter {9 @Override10 public boolean supports(Object handler) {11 return handler instanceof HandlerMethod;12 }13 14 @Override15 public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {16 // 调用控制器方法17 return invokeHandlerMethod(request, response, (HandlerMethod) handler);18 }19}1// ViewResolver接口2public interface ViewResolver {3 View resolveViewName(String viewName, Locale locale) throws Exception;4}56// View接口7public interface View {8 void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;9}1011// 实现类12public class InternalResourceViewResolver implements ViewResolver {13 private String prefix;14 private String suffix;15 16 @Override17 public View resolveViewName(String viewName, Locale locale) throws Exception {18 // 解析视图名称,如"user/list" -> "/WEB-INF/views/user/list.jsp"19 String viewPath = prefix + viewName + suffix;20 return new JstlView(viewPath);21 }22}3. 控制器开发
3.1 控制器基础
控制器是Spring MVC的核心组件,负责处理HTTP请求并返回响应。
1@Controller2@RequestMapping("/api/users")3public class UserController {4 5 @Autowired6 private UserService userService;7 8 // GET请求 - 获取用户列表9 @GetMapping10 public ResponseEntity<List<User>> getUsers() {11 List<User> users = userService.findAll();12 return ResponseEntity.ok(users);13 }14 15 // GET请求 - 获取单个用户16 @GetMapping("/{id}")17 public ResponseEntity<User> getUser(@PathVariable Long id) {18 User user = userService.findById(id);19 if (user != null) {20 return ResponseEntity.ok(user);21 }22 return ResponseEntity.notFound().build();23 }24 25 // POST请求 - 创建用户26 @PostMapping27 public ResponseEntity<User> createUser(@RequestBody User user) {28 User savedUser = userService.save(user);29 return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);30 }31 32 // PUT请求 - 更新用户33 @PutMapping("/{id}")34 public ResponseEntity<User> updateUser(@PathVariable Long id, @RequestBody User user) {35 user.setId(id);36 User updatedUser = userService.update(user);37 return ResponseEntity.ok(updatedUser);38 }39 40 // DELETE请求 - 删除用户41 @DeleteMapping("/{id}")42 public ResponseEntity<Void> deleteUser(@PathVariable Long id) {43 userService.deleteById(id);44 return ResponseEntity.noContent().build();45 }46}3.2 请求映射注解
Spring MVC提供了丰富的请求映射注解,支持各种HTTP方法和参数绑定:
- @RequestMapping
- HTTP方法注解
1// @RequestMapping - 通用映射2 @RequestMapping(value = "/users", method = RequestMethod.GET)3 public String getUsers() {4 return "user/list";5 }6 7// 可以在类级别使用8@Controller9@RequestMapping("/users")10public class UserController {11 // 方法映射会相对于类映射12 @RequestMapping("/{id}") // 实际路径为/users/{id}13 public String getUser(@PathVariable Long id) {14 return "user/detail";15 }16}1// @GetMapping - GET请求映射2 @GetMapping("/users/{id}")3 public String getUser(@PathVariable Long id) {4 return "user/detail";5 }6 7// @PostMapping - POST请求映射8 @PostMapping("/users")9 public String createUser(@ModelAttribute User user) {10 return "redirect:/users";11 }12 13// @PutMapping - PUT请求映射14 @PutMapping("/users/{id}")15 public String updateUser(@PathVariable Long id, @RequestBody User user) {16 return "user/updated";17 }18 19// @DeleteMapping - DELETE请求映射20 @DeleteMapping("/users/{id}")21 public String deleteUser(@PathVariable Long id) {22 return "user/deleted";23 }24 25// @PatchMapping - PATCH请求映射26 @PatchMapping("/users/{id}")27 public String patchUser(@PathVariable Long id, @RequestBody User user) {28 return "user/patched";29}- 使用具体的HTTP方法注解(@GetMapping、@PostMapping等)比@RequestMapping更清晰
- 对于RESTful API,推荐使用@RestController注解
- 对于传统Web应用,使用@Controller注解
3.3 参数绑定注解
Spring MVC提供了多种参数绑定注解,支持各种参数类型的自动绑定:
- @PathVariable
- @RequestParam
- @RequestBody
- 其他参数注解
1// @PathVariable - 路径变量2 @GetMapping("/users/{id}/orders/{orderId}")3 public String getUserOrder(@PathVariable Long id, @PathVariable Long orderId) {4 return "user/order";5 }1// @RequestParam - 请求参数2 @GetMapping("/search")3 public String search(@RequestParam String keyword, 4 @RequestParam(defaultValue = "1") int page,5 @RequestParam(required = false) String sort) {6 return "search/result";7 }1// @RequestBody - 请求体2 @PostMapping("/api/users")3 public ResponseEntity<User> createUser(@RequestBody User user) {4 User savedUser = userService.save(user);5 return ResponseEntity.ok(savedUser);6 }1// @ModelAttribute - 模型属性2 @PostMapping("/users")3 public String createUser(@ModelAttribute User user, Model model) {4 userService.save(user);5 model.addAttribute("message", "用户创建成功");6 return "user/success";7 }8 9// @RequestHeader - 请求头10 @GetMapping("/api/data")11 public ResponseEntity<String> getData(@RequestHeader("Authorization") String auth) {12 return ResponseEntity.ok("数据");13 }14 15// @CookieValue - Cookie值16 @GetMapping("/preferences")17 public String getPreferences(@CookieValue("theme") String theme) {18 return "preferences";19 }20 21// @SessionAttribute - 会话属性22 @GetMapping("/profile")23 public String getProfile(@SessionAttribute("user") User user) {24 return "user/profile";25 }26 27// @RequestPart - 文件上传28 @PostMapping("/upload")29 public String uploadFile(@RequestPart("file") MultipartFile file) {30 return "upload/success";31}4. 拦截器机制
4.1 拦截器基础
拦截器是Spring MVC的重要特性,允许在请求处理的不同阶段进行拦截和处理。
1// 实现HandlerInterceptor接口2public class LoggingInterceptor implements HandlerInterceptor {3 4 private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);5 6 @Override7 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {8 logger.info("请求开始: {} {}", request.getMethod(), request.getRequestURI());9 request.setAttribute("startTime", System.currentTimeMillis());10 return true; // 返回true继续执行,返回false中断请求11 }12 13 @Override14 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {15 logger.info("请求处理完成: {} {}", request.getMethod(), request.getRequestURI());16 }17 18 @Override19 public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {20 long startTime = (Long) request.getAttribute("startTime");21 long endTime = System.currentTimeMillis();22 logger.info("请求完成: {} {} 耗时: {}ms", request.getMethod(), request.getRequestURI(), endTime - startTime);23 }24}拦截器方法说明
| 方法 | 执行时机 | 用途 | 能否中断请求 |
|---|---|---|---|
| preHandle | 控制器方法执行前 | 前置检查、认证授权 | 可以 |
| postHandle | 控制器方法执行后,视图渲染前 | 模型数据修改、视图选择 | 不可以 |
| afterCompletion | 视图渲染完成后 | 清理资源、日志记录 | 不可以 |
4.2 拦截器应用
- 认证拦截器
- 权限拦截器
- 拦截器配置
1public class AuthenticationInterceptor implements HandlerInterceptor {2 3 @Override4 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {5 String token = request.getHeader("Authorization");6 7 if (token == null || !isValidToken(token)) {8 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);9 return false;10 }11 12 // 设置用户信息到请求属性13 User user = getUserFromToken(token);14 request.setAttribute("currentUser", user);15 16 return true;17 }18 19 private boolean isValidToken(String token) {20 return token != null && token.startsWith("Bearer ");21 }22 23 private User getUserFromToken(String token) {24 // 从token中获取用户信息25 return new User();26 }27}1public class AuthorizationInterceptor implements HandlerInterceptor {2 3 @Override4 public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {5 User user = (User) request.getAttribute("currentUser");6 7 if (user == null) {8 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);9 return false;10 }11 12 // 检查用户权限13 if (!hasPermission(user, request.getRequestURI())) {14 response.setStatus(HttpServletResponse.SC_FORBIDDEN);15 return false;16 }17 18 return true;19 }20 21 private boolean hasPermission(User user, String uri) {22 return user.getPermissions().contains(uri);23 }24}1@Configuration2public class WebMvcConfig implements WebMvcConfigurer {3 4 @Override5 public void addInterceptors(InterceptorRegistry registry) {6 // 添加日志拦截器7 registry.addInterceptor(new LoggingInterceptor())8 .addPathPatterns("/**")9 .excludePathPatterns("/static/**", "/error");10 11 // 添加认证拦截器12 registry.addInterceptor(new AuthenticationInterceptor())13 .addPathPatterns("/api/**")14 .excludePathPatterns("/api/login", "/api/register");15 16 // 添加权限拦截器17 registry.addInterceptor(new AuthorizationInterceptor())18 .addPathPatterns("/api/**")19 .excludePathPatterns("/api/login", "/api/register", "/api/public/**");20 }21}5. 异常处理
5.1 全局异常处理
Spring MVC提供了全局异常处理机制,可以统一处理应用中的异常:
- @ExceptionHandler
- @ControllerAdvice
- 异常解析器
1@Controller2public class UserController {3 4 // 控制器内的异常处理5 @ExceptionHandler(UserNotFoundException.class)6 public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {7 ErrorResponse error = new ErrorResponse();8 error.setMessage(ex.getMessage());9 error.setStatus(HttpStatus.NOT_FOUND.value());10 11 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);12 }13 14 @GetMapping("/users/{id}")15 public ResponseEntity<User> getUser(@PathVariable Long id) {16 User user = userService.findById(id);17 if (user == null) {18 throw new UserNotFoundException("用户不存在: " + id);19 }20 return ResponseEntity.ok(user);21 }22}1@ControllerAdvice2public class GlobalExceptionHandler {3 4 @ExceptionHandler(UserNotFoundException.class)5 public ResponseEntity<ErrorResponse> handleUserNotFoundException(UserNotFoundException ex) {6 ErrorResponse error = new ErrorResponse();7 error.setMessage(ex.getMessage());8 error.setStatus(HttpStatus.NOT_FOUND.value());9 10 return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);11 }12 13 @ExceptionHandler(ValidationException.class)14 public ResponseEntity<ErrorResponse> handleValidationException(ValidationException ex) {15 ErrorResponse error = new ErrorResponse();16 error.setMessage("参数验证失败");17 error.setStatus(HttpStatus.BAD_REQUEST.value());18 error.setErrors(ex.getErrors());19 20 return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error);21 }22 23 @ExceptionHandler(Exception.class)24 public ResponseEntity<ErrorResponse> handleGenericException(Exception ex) {25 ErrorResponse error = new ErrorResponse();26 error.setMessage("服务器内部错误");27 error.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());28 29 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);30 }31}1@Configuration2public class WebMvcConfig implements WebMvcConfigurer {3 4 @Override5 public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {6 SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();7 8 // 配置异常映射9 Properties mappings = new Properties();10 mappings.setProperty("UserNotFoundException", "error/not-found");11 mappings.setProperty("AccessDeniedException", "error/access-denied");12 exceptionResolver.setExceptionMappings(mappings);13 14 // 设置默认错误页15 exceptionResolver.setDefaultErrorView("error/generic");16 exceptionResolver.setExceptionAttribute("exception");17 18 resolvers.add(exceptionResolver);19 }20}5.2 自定义异常
1// 用户不存在异常2public class UserNotFoundException extends RuntimeException {3 public UserNotFoundException(String message) {4 super(message);5 }6}78// 验证异常9public class ValidationException extends RuntimeException {10 private List<String> errors;11 12 public ValidationException(String message, List<String> errors) {13 super(message);14 this.errors = errors;15 }16 17 public List<String> getErrors() {18 return errors;19 }20}6. 数据验证
6.1 Bean校验
Spring MVC集成了Bean Validation API,支持声明式数据验证:
1// 验证模型2public class UserDTO {3 @NotBlank(message = "用户名不能为空")4 @Size(min = 4, max = 50, message = "用户名长度必须在4-50之间")5 private String username;6 7 @NotBlank(message = "密码不能为空")8 @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$", message = "密码至少8位,包含字母和数字")9 private String password;10 11 @NotBlank(message = "电子邮件不能为空")12 @Email(message = "电子邮件格式不正确")13 private String email;14 15 @Min(value = 18, message = "年龄必须大于等于18")16 private int age;17 18 // getter和setter方法19}2021// 控制器中使用22@RestController23@RequestMapping("/api/users")24public class UserController {25 26 @PostMapping("/register")27 public ResponseEntity<?> register(@Valid @RequestBody UserDTO userDTO, BindingResult result) {28 if (result.hasErrors()) {29 Map<String, String> errors = new HashMap<>();30 result.getFieldErrors().forEach(err -> 31 errors.put(err.getField(), err.getDefaultMessage())32 );33 return ResponseEntity.badRequest().body(errors);34 }35 36 // 业务处理37 return ResponseEntity.ok("用户注册成功");38 }39}6.2 常用校验注解
| 注解 | 说明 | 示例 |
|---|---|---|
| @NotNull | 不能为null | @NotNull(message = "不能为空") |
| @NotEmpty | 不能为空字符串或集合 | @NotEmpty(message = "不能为空") |
| @NotBlank | 不能为空白字符串 | @NotBlank(message = "不能为空白") |
| @Size | 长度或大小限制 | @Size(min = 2, max = 10) |
| @Min/@Max | 最小/最大值 | @Min(value = 18) |
| @Pattern | 正则表达式校验 | @Pattern(regexp = "\\d+") |
| 电子邮件格式 | @Email(message = "邮箱格式不正确") | |
| @Future/@Past | 日期在当前时间之后/之前 | @Future(message = "必须是将来时间") |
| @AssertTrue | 必须为true | @AssertTrue(message = "必须接受协议") |
6.3 分组校验
1// 定义验证组2public interface Create {}3public interface Update {}45// 模型使用分组6public class UserDTO {7 @NotNull(groups = {Update.class})8 private Long id;9 10 @NotBlank(groups = {Create.class, Update.class})11 private String username;12 13 @NotBlank(groups = {Create.class})14 @Null(groups = {Update.class})15 private String password;16 17 // getter和setter方法18}1920// 控制器使用分组21@RestController22@RequestMapping("/api/users")23public class UserController {24 25 @PostMapping26 public ResponseEntity<?> create(@Validated(Create.class) @RequestBody UserDTO user, BindingResult result) {27 if (result.hasErrors()) {28 return ResponseEntity.badRequest().body(getErrors(result));29 }30 return ResponseEntity.ok("用户创建成功");31 }32 33 @PutMapping("/{id}")34 public ResponseEntity<?> update(@Validated(Update.class) @RequestBody UserDTO user, BindingResult result) {35 if (result.hasErrors()) {36 return ResponseEntity.badRequest().body(getErrors(result));37 }38 return ResponseEntity.ok("用户更新成功");39 }40 41 private Map<String, String> getErrors(BindingResult result) {42 Map<String, String> errors = new HashMap<>();43 result.getFieldErrors().forEach(err -> 44 errors.put(err.getField(), err.getDefaultMessage())45 );46 return errors;47 }48}7. 文件上传
7.1 文件上传配置
Spring MVC提供了内置的文件上传支持,可以轻松处理文件上传请求:
- 上传配置
- 控制器实现
- 多文件上传
1@Configuration2public class FileUploadConfig {3 4 @Bean5 public MultipartResolver multipartResolver() {6 CommonsMultipartResolver resolver = new CommonsMultipartResolver();7 resolver.setMaxUploadSize(10 * 1024 * 1024); // 10MB8 resolver.setMaxUploadSizePerFile(5 * 1024 * 1024); // 5MB9 resolver.setDefaultEncoding("UTF-8");10 return resolver;11 }12}1314// 或使用SpringBoot中的配置15@SpringBootApplication16public class Application {17 18 public static void main(String[] args) {19 SpringApplication.run(Application.class, args);20 }21}2223// application.yml24/*25spring:26 servlet:27 multipart:28 max-file-size: 5MB29 max-request-size: 10MB30 enabled: true31*/1@Controller2public class FileUploadController {3 4 @Value("${file.upload-dir}")5 private String uploadDir;6 7 @PostMapping("/upload")8 public String uploadFile(@RequestParam("file") MultipartFile file, Model model) {9 if (file.isEmpty()) {10 model.addAttribute("message", "请选择文件");11 return "upload/form";12 }13 14 try {15 // 生成文件名16 String fileName = UUID.randomUUID().toString() + 17 "_" + file.getOriginalFilename();18 19 // 确保目录存在20 Path uploadPath = Paths.get(uploadDir);21 if (!Files.exists(uploadPath)) {22 Files.createDirectories(uploadPath);23 }24 25 // 保存文件26 Path filePath = uploadPath.resolve(fileName);27 Files.copy(file.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);28 29 model.addAttribute("message", "文件上传成功: " + fileName);30 return "upload/success";31 } catch (IOException e) {32 model.addAttribute("message", "文件上传失败: " + e.getMessage());33 return "upload/form";34 }35 }36 37 @GetMapping("/upload")38 public String showUploadForm() {39 return "upload/form";40 }41 }1@Controller2public class MultiFileUploadController {3 4 @PostMapping("/upload/multi")5 public String uploadMultipleFiles(@RequestParam("files") MultipartFile[] files, Model model) {6 List<String> uploadedFiles = new ArrayList<>();7 8 for (MultipartFile file : files) {9 if (!file.isEmpty()) {10 try {11 String fileName = saveFile(file);12 uploadedFiles.add(fileName);13 } catch (IOException e) {14 model.addAttribute("message", "文件上传失败: " + e.getMessage());15 return "upload/form";16 }17 }18 }19 20 model.addAttribute("message", "上传了 " + uploadedFiles.size() + " 个文件");21 model.addAttribute("files", uploadedFiles);22 return "upload/success";23 }24 25 private String saveFile(MultipartFile file) throws IOException {26 // 文件保存逻辑27 return "savedFileName.ext";28 }29}7.2 文件上传视图
1<!-- upload/form.html -->2<!DOCTYPE html>3<html xmlns:th="http://www.thymeleaf.org">4<head>5 <title>文件上传</title>6</head>7<body>8 <h1>文件上传</h1>9 10 <div th:if="${message}">11 <p th:text="${message}"></p>12 </div>13 14 <form method="POST" action="/upload" enctype="multipart/form-data">15 <div>16 <label for="file">选择文件:</label>17 <input type="file" id="file" name="file" />18 </div>19 <div>20 <button type="submit">上传</button>21 </div>22 </form>23 24 <h2>多文件上传</h2>25 <form method="POST" action="/upload/multi" enctype="multipart/form-data">26 <div>27 <label for="files">选择文件:</label>28 <input type="file" id="files" name="files" multiple />29 </div>30 <div>31 <button type="submit">上传</button>32 </div>33 </form>34</body>35</html>8. 视图技术
8.1 视图解析器
Spring MVC支持多种视图技术,通过视图解析器将逻辑视图名解析为实际的视图对象:
- JSP视图
- Thymeleaf视图
- 多视图解析器
1@Configuration2public class WebConfig implements WebMvcConfigurer {3 4 @Bean5 public ViewResolver jspViewResolver() {6 InternalResourceViewResolver resolver = new InternalResourceViewResolver();7 resolver.setPrefix("/WEB-INF/views/");8 resolver.setSuffix(".jsp");9 resolver.setViewClass(JstlView.class);10 return resolver;11 }12}1314// 控制器15@Controller16public class UserController {17 18 @GetMapping("/users")19 public String listUsers(Model model) {20 // 逻辑视图名 "user/list" 解析为 "/WEB-INF/views/user/list.jsp"21 return "user/list";22 }23}1@Configuration2public class ThymeleafConfig {3 4 @Bean5 public SpringTemplateEngine templateEngine() {6 SpringTemplateEngine engine = new SpringTemplateEngine();7 engine.setTemplateResolver(templateResolver());8 return engine;9 }10 11 @Bean12 public ThymeleafViewResolver thymeleafViewResolver() {13 ThymeleafViewResolver resolver = new ThymeleafViewResolver();14 resolver.setTemplateEngine(templateEngine());15 resolver.setCharacterEncoding("UTF-8");16 return resolver;17 }18 19 @Bean20 public SpringResourceTemplateResolver templateResolver() {21 SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();22 resolver.setPrefix("classpath:/templates/");23 resolver.setSuffix(".html");24 resolver.setTemplateMode(TemplateMode.HTML);25 resolver.setCharacterEncoding("UTF-8");26 return resolver;27 }28}1@Configuration2public class MultipleViewResolversConfig implements WebMvcConfigurer {3 4 @Bean5 public ViewResolver thymeleafViewResolver() {6 ThymeleafViewResolver resolver = new ThymeleafViewResolver();7 resolver.setTemplateEngine(templateEngine());8 resolver.setCharacterEncoding("UTF-8");9 resolver.setOrder(1);10 resolver.setViewNames(new String[] {"*.html"});11 return resolver;12 }13 14 @Bean15 public ViewResolver jspViewResolver() {16 InternalResourceViewResolver resolver = new InternalResourceViewResolver();17 resolver.setPrefix("/WEB-INF/views/");18 resolver.setSuffix(".jsp");19 resolver.setViewClass(JstlView.class);20 resolver.setOrder(2);21 return resolver;22 }23}8.2 视图内容协商
Spring MVC支持内容协商,可以根据请求的Accept头或URL参数返回不同格式的响应:
1@Configuration2public class ContentNegotiationConfig implements WebMvcConfigurer {3 4 @Override5 public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {6 // 通过URL参数进行内容协商7 configurer8 .favorParameter(true)9 .parameterName("format")10 .ignoreAcceptHeader(false)11 .defaultContentType(MediaType.APPLICATION_JSON)12 .mediaType("json", MediaType.APPLICATION_JSON)13 .mediaType("xml", MediaType.APPLICATION_XML);14 }15 16 @Override17 public void configureViewResolvers(ViewResolverRegistry registry) {18 registry.enableContentNegotiation(19 new MappingJackson2JsonView(),20 new MarshallingView(jaxb2Marshaller()),21 pdfView()22 );23 }24 25 @Bean26 public Jaxb2Marshaller jaxb2Marshaller() {27 Jaxb2Marshaller marshaller = new Jaxb2Marshaller();28 marshaller.setPackagesToScan("com.example.model");29 return marshaller;30 }31 32 @Bean33 public View pdfView() {34 return new PdfView();35 }36}3738// 控制器39@Controller40public class UserController {41 42 @GetMapping("/users/{id}")43 public User getUser(@PathVariable Long id) {44 // 根据内容协商返回JSON或XML45 return userService.findById(id);46 }47}9. RESTful API
9.1 RESTful API设计
RESTful API设计是构建现代Web服务的重要范式,Spring MVC提供了全面的支持:
| 原则 | 说明 | 示例 |
|---|---|---|
| 资源导向 | 使用名词而非动词 | /users 而不是 /getUsers |
| HTTP方法语义 | 使用正确的HTTP方法 | GET、POST、PUT、DELETE |
| 无状态 | 每个请求包含完整信息 | 不依赖会话状态 |
| 统一接口 | 使用标准HTTP状态码 | 200、201、400、404等 |
| 可缓存 | 支持缓存机制 | 使用ETag、Cache-Control |
9.2 RESTful API实现
- 基础实现
- HATEOAS支持
1@RestController2@RequestMapping("/api/users")3public class UserRestController {4 5 @Autowired6 private UserService userService;7 8 // GET /api/users - 获取用户列表9 @GetMapping10 public ResponseEntity<List<User>> getUsers(11 @RequestParam(defaultValue = "0") int page,12 @RequestParam(defaultValue = "10") int size) {13 14 List<User> users = userService.findAll(page, size);15 return ResponseEntity.ok(users);16 }17 18 // GET /api/users/{id} - 获取单个用户19 @GetMapping("/{id}")20 public ResponseEntity<User> getUser(@PathVariable Long id) {21 User user = userService.findById(id);22 if (user == null) {23 return ResponseEntity.notFound().build();24 }25 return ResponseEntity.ok(user);26 }27 28 // POST /api/users - 创建用户29 @PostMapping30 public ResponseEntity<User> createUser(@Valid @RequestBody User user) {31 User savedUser = userService.save(user);32 return ResponseEntity.status(HttpStatus.CREATED)33 .header("Location", "/api/users/" + savedUser.getId())34 .body(savedUser);35 }36 37 // PUT /api/users/{id} - 更新用户38 @PutMapping("/{id}")39 public ResponseEntity<User> updateUser(@PathVariable Long id, @Valid @RequestBody User user) {40 user.setId(id);41 User updatedUser = userService.update(user);42 return ResponseEntity.ok(updatedUser);43 }44 45 // DELETE /api/users/{id} - 删除用户46 @DeleteMapping("/{id}")47 public ResponseEntity<Void> deleteUser(@PathVariable Long id) {48 userService.deleteById(id);49 return ResponseEntity.noContent().build();50 }51 }1@RestController2@RequestMapping("/api/users")3public class UserHateoasController {4 5 @Autowired6 private UserService userService;7 8 @GetMapping("/{id}")9 public EntityModel<User> getUser(@PathVariable Long id) {10 User user = userService.findById(id);11 if (user == null) {12 throw new UserNotFoundException(id);13 }14 15 // 创建超链接16 EntityModel<User> resource = EntityModel.of(user);17 18 // 添加自引用链接19 resource.add(linkTo(methodOn(UserHateoasController.class)20 .getUser(id)).withSelfRel());21 22 // 添加用户集合链接23 resource.add(linkTo(methodOn(UserHateoasController.class)24 .getUsers(0, 10)).withRel("users"));25 26 // 添加用户订单链接27 resource.add(linkTo(methodOn(OrderController.class)28 .getUserOrders(id)).withRel("orders"));29 30 return resource;31 }32 33 @GetMapping34 public CollectionModel<EntityModel<User>> getUsers(35 @RequestParam(defaultValue = "0") int page,36 @RequestParam(defaultValue = "10") int size) {37 38 List<User> users = userService.findAll(page, size);39 40 List<EntityModel<User>> resources = users.stream()41 .map(user -> EntityModel.of(user, 42 linkTo(methodOn(UserHateoasController.class)43 .getUser(user.getId())).withSelfRel(),44 linkTo(methodOn(UserHateoasController.class)45 .getUsers(page, size)).withRel("users")))46 .collect(Collectors.toList());47 48 return CollectionModel.of(resources, 49 linkTo(methodOn(UserHateoasController.class)50 .getUsers(page, size)).withSelfRel());51 }52}9.3 API版本控制
1// 1. URL路径版本控制2@RestController3@RequestMapping("/api/v1/users")4public class UserApiV1Controller {5 // V1版本的API6}78@RestController9@RequestMapping("/api/v2/users")10public class UserApiV2Controller {11 // V2版本的API12}1314// 2. 请求参数版本控制15@RestController16@RequestMapping("/api/users")17public class UserApiVersionController {18 19 @GetMapping(params = "version=1")20 public ResponseEntity<UserV1> getUserV1(@RequestParam Long id) {21 // V1版本的处理逻辑22 }23 24 @GetMapping(params = "version=2")25 public ResponseEntity<UserV2> getUserV2(@RequestParam Long id) {26 // V2版本的处理逻辑27 }28}2930// 3. 请求头版本控制31@RestController32@RequestMapping("/api/users")33public class UserApiHeaderVersionController {34 35 @GetMapping(headers = "X-API-Version=1")36 public ResponseEntity<UserV1> getUserV1(@RequestParam Long id) {37 // V1版本的处理逻辑38 }39 40 @GetMapping(headers = "X-API-Version=2")41 public ResponseEntity<UserV2> getUserV2(@RequestParam Long id) {42 // V2版本的处理逻辑43 }44}4546// 4. Accept头版本控制47@RestController48@RequestMapping("/api/users")49public class UserApiAcceptVersionController {50 51 @GetMapping(produces = "application/vnd.company.app-v1+json")52 public ResponseEntity<UserV1> getUserV1(@RequestParam Long id) {53 // V1版本的处理逻辑54 }55 56 @GetMapping(produces = "application/vnd.company.app-v2+json")57 public ResponseEntity<UserV2> getUserV2(@RequestParam Long id) {58 // V2版本的处理逻辑59 }60}10. 面试题精选
10.1 基础概念题
- MVC流程
- 注解区别
Q: Spring MVC的请求处理流程是怎样的?
A: Spring MVC的请求处理流程如下:
- 请求接收:客户端发送HTTP请求到DispatcherServlet
- Handler映射:通过HandlerMapping找到对应的Handler
- Handler适配:通过HandlerAdapter适配并执行Handler
- 业务处理:Handler执行业务逻辑,返回ModelAndView
- 视图解析:通过ViewResolver解析视图名称
- 视图渲染:渲染视图并返回响应
整个过程由DispatcherServlet统一调度,实现了前端控制器模式,使各组件解耦并协同工作。
Q: @RequestMapping和@GetMapping有什么区别?
A:
- @RequestMapping是通用注解,可以指定method属性
- @GetMapping是
@RequestMapping(method = RequestMethod.GET)的简写 - @GetMapping更简洁,专门用于GET请求
- @RequestMapping更灵活,可以处理多种HTTP方法
- 类似的还有@PostMapping、@PutMapping、@DeleteMapping、@PatchMapping,都是对应HTTP方法的简写形式
10.2 实践题
- RESTful API
- 拦截器作用
Q: 如何实现RESTful API?
A: 实现RESTful API需要:
- 使用
@RestController注解 - 使用
@GetMapping、@PostMapping等注解匹配HTTP方法 - 返回
ResponseEntity对象控制HTTP状态码和响应头 - 使用
@PathVariable处理路径参数 - 使用
@RequestBody处理请求体 - 遵循REST设计原则:资源命名、HTTP方法语义、无状态、统一接口等
- 使用适当的HTTP状态码:200(OK)、201(Created)、204(No Content)、400(Bad Request)、404(Not Found)等
对于更高级的RESTful API,还可以考虑使用HATEOAS添加超链接,使API更具自描述性。
Q: Spring MVC的拦截器有什么作用?
A: Spring MVC的拦截器可以在请求处理的不同阶段进行拦截和处理,主要作用包括:
- 身份验证:验证用户是否登录
- 授权检查:检查用户是否有权限访问资源
- 日志记录:记录请求日志,如IP、请求参数、执行时间等
- 性能监控:监控请求执行时间
- 数据转换:在请求前后对数据进行转换
- 国际化:根据请求设置语言环境
- 主题设置:根据用户设置主题
拦截器通过实现HandlerInterceptor接口,可以在请求处理的preHandle(前)、postHandle(后)和afterCompletion(完成)三个阶段执行自定义逻辑。
10.3 高级题
- 异步处理
- 内容协商
Q: Spring MVC如何实现异步请求处理?
A: Spring MVC支持三种异步请求处理方式:
- 返回Callable对象:
1@GetMapping("/async")2public Callable<String> asyncTask() {3 return () -> {4 // 异步任务5 Thread.sleep(5000);6 return "Async result";7 };8}- 返回DeferredResult对象:
1@GetMapping("/async-deferred")2public DeferredResult<String> asyncDeferredResult() {3 DeferredResult<String> result = new DeferredResult<>();4 taskExecutor.execute(() -> {5 try {6 Thread.sleep(5000);7 result.setResult("Deferred result");8 } catch (Exception e) {9 result.setErrorResult(e);10 }11 });12 return result;13}- 返回CompletableFuture对象:
1@GetMapping("/async-future")2public CompletableFuture<String> asyncFuture() {3 return CompletableFuture.supplyAsync(() -> {4 try {5 Thread.sleep(5000);6 return "Future result";7 } catch (Exception e) {8 throw new RuntimeException(e);9 }10 });11}异步请求处理的好处是可以释放Web容器线程,提高系统吞吐量,特别是对于长时间运行的任务。
Q: Spring MVC的内容协商是什么?如何实现?
A: 内容协商(Content Negotiation)是指根据客户端的请求,返回不同格式(如JSON、XML、PDF等)的响应数据。
实现方式:
- 基于请求头Accept:根据客户端发送的Accept头选择合适的响应格式
- 基于URL参数:通过URL参数指定响应格式,如
?format=json - 基于文件扩展名:通过URL路径扩展名指定格式,如
/users.json
Spring MVC实现内容协商的方法:
1@Configuration2public class WebConfig implements WebMvcConfigurer {3 4 @Override5 public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {6 configurer7 .favorParameter(true)8 .parameterName("format")9 .ignoreAcceptHeader(false)10 .defaultContentType(MediaType.APPLICATION_JSON)11 .mediaType("json", MediaType.APPLICATION_JSON)12 .mediaType("xml", MediaType.APPLICATION_XML);13 }14}1516@GetMapping("/users/{id}")17public User getUser(@PathVariable Long id) {18 // 根据内容协商返回JSON或XML19 return userService.findById(id);20}- 理解核心组件:掌握DispatcherServlet、HandlerMapping、HandlerAdapter等核心组件
- 熟悉请求流程:理解从请求到响应的完整处理流程
- 掌握控制器开发:学会使用@Controller、@RestController等注解开发控制器
- 了解视图技术:熟悉JSP、Thymeleaf等视图技术
- 学会RESTful API:掌握RESTful API设计和实现
- 熟悉数据验证:学会使用Bean Validation进行数据验证
- 掌握拦截器机制:学会自定义拦截器处理横切关注点
通过本章的学习,你应该已经掌握了Spring MVC的核心概念、请求处理流程和实际应用。Spring MVC是构建Java Web应用的重要框架,掌握其原理和使用方法对于开发高质量的Web应用至关重要。无论是传统Web应用还是现代RESTful API,Spring MVC都能提供强大而灵活的支持。
评论