Spring MVC 基础
1. Spring MVC 概述
1.1 什么是 Spring MVC
Spring MVC 是 Spring Framework 的 Web 模块,基于 Model-View-Controller 设计模式实现的 Web 框架。
1.2 MVC 设计模式
| 组件 | 职责 |
|---|---|
| Model(模型) | 业务逻辑处理,数据封装 |
| View(视图) | 页面展示,用户界面 |
| Controller(控制器) | 请求分发,协调 Model 和 View |
1.3 Spring MVC 架构
客户端请求 → DispatcherServlet → HandlerMapping → Controller
↓
客户端响应 ← ViewResolver ← View ← ModelAndView核心组件:
- DispatcherServlet:前端控制器,统一请求分发
- HandlerMapping:处理器映射器,找到对应的 Controller
- HandlerAdapter:处理器适配器,执行 Controller 方法
- ViewResolver:视图解析器,解析视图页面
- ModelAndView:模型和视图的封装对象
2. 快速入门
2.1 添加依赖
xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>7.0.0</version>
</dependency>
<dependency>
<groupId>jakarta.servlet</groupId>
<artifactId>jakarta.servlet-api</artifactId>
<version>6.1.0</version>
<scope>provided</scope>
</dependency>2.2 配置 DispatcherServlet
java
public class WebAppInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext context =
new AnnotationConfigWebApplicationContext();
context.register(WebConfig.class);
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration =
servletContext.addServlet("dispatcher", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/");
}
}2.3 配置类
java
@Configuration
@EnableWebMvc
@ComponentScan("com.example.controller")
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("/static/");
}
}3. 控制器开发
3.1 @Controller 注解
java
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/list")
public String list() {
return "user/list";
}
}3.2 @RequestMapping 注解
java
@Controller
@RequestMapping("/api")
public class ApiController {
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String hello() {
return "hello";
}
@GetMapping("/users")
public String getUsers() {
return "users";
}
@PostMapping("/users")
public String createUser() {
return "redirect:/api/users";
}
@PutMapping("/users/{id}")
public String updateUser(@PathVariable Long id) {
return "redirect:/api/users";
}
@DeleteMapping("/users/{id}")
public String deleteUser(@PathVariable Long id) {
return "redirect:/api/users";
}
}3.3 RESTful 控制器
java
@RestController
@RequestMapping("/api/v1/products")
public class ProductController {
private final ProductService productService;
public ProductController(ProductService productService) {
this.productService = productService;
}
@GetMapping
public List<Product> getAll() {
return productService.findAll();
}
@GetMapping("/{id}")
public Product getById(@PathVariable Long id) {
return productService.findById(id);
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Product create(@RequestBody Product product) {
return productService.save(product);
}
@PutMapping("/{id}")
public Product update(@PathVariable Long id, @RequestBody Product product) {
product.setId(id);
return productService.save(product);
}
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void delete(@PathVariable Long id) {
productService.deleteById(id);
}
}4. 请求参数处理
4.1 基本参数
java
@GetMapping("/search")
public String search(@RequestParam String keyword,
@RequestParam(defaultValue = "1") int page,
@RequestParam(required = false) String category) {
return "search";
}4.2 路径变量
java
@GetMapping("/products/{category}/{id}")
public String getProduct(@PathVariable String category,
@PathVariable Long id) {
return "product";
}
@GetMapping("/users/{userId}/posts/{postId}")
public String getPost(@PathVariable Long userId,
@PathVariable Long postId) {
return "post";
}4.3 请求体
java
@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}4.4 请求头
java
@GetMapping("/info")
public String getInfo(@RequestHeader("User-Agent") String userAgent,
@RequestHeader(value = "X-Token", required = false) String token) {
return "info";
}4.5 Cookie
java
@GetMapping("/profile")
public String getProfile(@CookieValue(value = "sessionId", required = false)
String sessionId) {
return "profile";
}4.6 表单对象
java
@PostMapping("/register")
public String register(@ModelAttribute UserForm form) {
userService.register(form);
return "redirect:/login";
}
public record UserForm(
String username,
String password,
String email
) {}5. 响应处理
5.1 返回视图
java
@GetMapping("/home")
public String home() {
return "home";
}
@GetMapping("/profile")
public String profile(Model model) {
model.addAttribute("user", userService.getCurrentUser());
return "profile";
}5.2 返回 JSON
java
@GetMapping("/api/user")
@ResponseBody
public User getUser() {
return userService.getCurrentUser();
}
@RestController
public class ApiController {
@GetMapping("/api/data")
public Map<String, Object> getData() {
return Map.of("code", 200, "data", "success");
}
}5.3 ResponseEntity
java
@GetMapping("/download")
public ResponseEntity<Resource> download() {
Resource resource = new ClassPathResource("files/document.pdf");
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=document.pdf")
.body(resource);
}
@PostMapping("/users")
public ResponseEntity<User> createUser(@RequestBody User user) {
User saved = userService.save(user);
URI location = URI.create("/api/users/" + saved.getId());
return ResponseEntity.created(location).body(saved);
}6. 数据验证
6.1 添加依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>6.2 验证注解
java
public record UserRequest(
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度3-20位")
String username,
@NotBlank(message = "密码不能为空")
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d]{8,}$",
message = "密码至少8位,包含字母和数字")
String password,
@Email(message = "邮箱格式不正确")
String email,
@Min(value = 18, message = "年龄必须大于等于18")
@Max(value = 100, message = "年龄必须小于等于100")
Integer age
) {}6.3 控制器验证
java
@PostMapping("/users")
public ResponseEntity<?> createUser(
@Valid @RequestBody UserRequest request,
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);
}
return ResponseEntity.ok(userService.create(request));
}
@PutMapping("/users/{id}")
public ResponseEntity<?> updateUser(
@PathVariable Long id,
@Valid @RequestBody UserRequest request) {
return ResponseEntity.ok(userService.update(id, request));
}7. 异常处理
7.1 @ExceptionHandler
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException e) {
ErrorResponse error = new ErrorResponse(
HttpStatus.NOT_FOUND.value(),
e.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException e) {
Map<String, String> errors = new HashMap<>();
e.getBindingResult().getFieldErrors().forEach(error ->
errors.put(error.getField(), error.getDefaultMessage()));
ErrorResponse error = new ErrorResponse(
HttpStatus.BAD_REQUEST.value(),
"参数验证失败",
errors
);
return ResponseEntity.badRequest().body(error);
}
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
ErrorResponse error = new ErrorResponse(
HttpStatus.INTERNAL_SERVER_ERROR.value(),
"服务器内部错误",
System.currentTimeMillis()
);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}
public record ErrorResponse(
int status,
String message,
Object data
) {}7.2 @ResponseStatus
java
@ResponseStatus(code = HttpStatus.NOT_FOUND, reason = "资源未找到")
public class ResourceNotFoundException extends RuntimeException {
public ResourceNotFoundException(String message) {
super(message);
}
}8. 拦截器
8.1 定义拦截器
java
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !token.startsWith("Bearer ")) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
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 {
}
}8.2 注册拦截器
java
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/api/**")
.excludePathPatterns("/api/auth/**", "/api/public/**");
}
}9. 文件上传下载
9.1 文件上传
java
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空";
}
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
Path path = Paths.get("uploads/" + fileName);
try {
Files.createDirectories(path.getParent());
Files.copy(file.getInputStream(), path);
return "上传成功: " + fileName;
} catch (IOException e) {
return "上传失败: " + e.getMessage();
}
}9.2 多文件上传
java
@PostMapping("/uploads")
public String uploadMultiple(@RequestParam("files") MultipartFile[] files) {
List<String> fileNames = new ArrayList<>();
for (MultipartFile file : files) {
if (!file.isEmpty()) {
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
Path path = Paths.get("uploads/" + fileName);
try {
Files.copy(file.getInputStream(), path);
fileNames.add(fileName);
} catch (IOException e) {
return "上传失败";
}
}
}
return "上传成功: " + String.join(", ", fileNames);
}9.3 文件下载
java
@GetMapping("/download/{fileName}")
public ResponseEntity<Resource> download(@PathVariable String fileName) {
Path path = Paths.get("uploads/" + fileName);
Resource resource = new FileSystemResource(path);
if (!resource.exists()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + fileName + "\"")
.body(resource);
}10. Spring 7 新特性
10.1 HTTP Service Clients
Spring 7 引入了声明式 HTTP 客户端:
java
@HttpServiceClient
public interface UserClient {
@GetExchange("/users/{id}")
User getUser(@PathVariable Long id);
@PostExchange("/users")
User createUser(@RequestBody User user);
@GetExchange("/users")
List<User> getAllUsers();
}
@Configuration
public class HttpConfig {
@Bean
public UserClient userClient() {
return HttpServiceClientProxyFactory.builder()
.baseUrl("https://api.example.com")
.build(UserClient.class);
}
}10.2 API 版本控制
java
@RestController
@RequestMapping("/api")
public class VersionedController {
@GetMapping(value = "/users", headers = "X-API-Version=1")
public List<UserV1> getUsersV1() {
return userService.findAllV1();
}
@GetMapping(value = "/users", headers = "X-API-Version=2")
public List<UserV2> getUsersV2() {
return userService.findAllV2();
}
}11. 最佳实践
11.1 统一响应格式
java
public record ApiResponse<T>(
int code,
String message,
T data,
long timestamp
) {
public static <T> ApiResponse<T> success(T data) {
return new ApiResponse<>(200, "success", data, System.currentTimeMillis());
}
public static <T> ApiResponse<T> error(int code, String message) {
return new ApiResponse<>(code, message, null, System.currentTimeMillis());
}
}
@RestController
public class UserController {
@GetMapping("/users/{id}")
public ApiResponse<User> getUser(@PathVariable Long id) {
return ApiResponse.success(userService.findById(id));
}
}11.2 分页查询
java
@GetMapping("/users")
public Page<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(defaultValue = "id,asc") String sort) {
Pageable pageable = PageRequest.of(page, size, Sort.by(sort).ascending());
return userService.findAll(pageable);
}12. 小结
本章学习了 Spring MVC 的核心概念和开发技能:
| 内容 | 要点 |
|---|---|
| 架构 | DispatcherServlet、HandlerMapping、ViewResolver |
| 控制器 | @Controller、@RestController、@RequestMapping |
| 参数处理 | @RequestParam、@PathVariable、@RequestBody、@RequestHeader |
| 响应处理 | 视图返回、JSON返回、ResponseEntity |
| 数据验证 | @Valid、@NotBlank、@Size、@Pattern |
| 异常处理 | @ExceptionHandler、@RestControllerAdvice |
| 拦截器 | HandlerInterceptor、preHandle、postHandle |
| 文件操作 | MultipartFile、上传、下载 |
下一章将学习 Spring MVC 的高级特性。