Appearance
Spring MVC
MVC 架构
┌─────────────────────────────────────────────────────────────────┐
│ Spring MVC 请求处理流程 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 请求 ──→ DispatcherServlet ──→ HandlerMapping │
│ ↓ │
│ HandlerAdapter │
│ ↓ │
│ Controller │
│ ↓ │
│ ViewResolver │
│ ↓ │
│ View │
│ ↓ │
│ 响应 │
│ │
└─────────────────────────────────────────────────────────────────┘@Controller 与 @RestController
java
// 普通 Controller(返回视图)
@Controller
public class UserController {
@RequestMapping("/user")
public String getUser(Model model) {
model.addAttribute("user", userService.getUser());
return "userDetail"; // 视图名
}
}
// REST Controller(返回数据)
@RestController
@RequestMapping("/api/users")
public class UserApiController {
@GetMapping("/{id}")
public User getUser(@PathVariable Long id) {
return userService.getUser(id);
}
// @RestController = @Controller + @ResponseBody
// 所有方法自动返回 JSON
}请求映射
@RequestMapping
java
@RestController
@RequestMapping("/api/users")
public class UserController {
// GET 请求
@GetMapping
public List<User> listUsers() { ... }
// POST 请求
@PostMapping
public User createUser(@RequestBody User user) { ... }
// PUT 请求
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) { ... }
// DELETE 请求
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) { ... }
// 组合注解
@GetMapping("/{id}")
@PostMapping("/search")
}请求参数绑定
java
@GetMapping("/user")
public User getUser(
@PathVariable Long id, // 路径变量
@RequestParam String name, // 请求参数
@RequestParam(defaultValue = "10") int pageSize, // 默认值
@RequestParam(required = false) String email, // 可选
@RequestHeader String authorization, // 请求头
@CookieValue("JSESSIONID") String sessionId // Cookie
) { ... }
// 实体类绑定
@PostMapping("/user")
public User createUser(@RequestBody User user) { ... }数据绑定
简单类型绑定
java
// String -> int
@GetMapping("/user/{id}")
public User getUser(@PathVariable("id") Long id) { ... }
// 日期格式化
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(Date.class,
new CustomDateEditor(new SimpleDateFormat("yyyy-MM-dd"), false));
}复杂对象绑定
java
// 嵌套对象
public class CreateUserRequest {
private String name;
private Integer age;
private Address address; // 嵌套对象
// getters and setters
}
public class Address {
private String city;
private String street;
// getters and setters
}
// HTTP 请求
// name=zhang&age=25&address.city=beijing&address.street=chaoyang数组和集合绑定
java
// 数组参数
@GetMapping("/search")
public List<User> search(
@RequestParam String[] interests
) { ... }
// URL: /search?interests=java&interests=spring
// List 参数
@GetMapping("/search")
public List<User> search(
@RequestParam List<String> interests
) { ... }数据校验
@Valid 与 @Validated
java
@RestController
@RequestMapping("/api/users")
@Validated
public class UserController {
@PostMapping
public User createUser(@Valid @RequestBody UserRequest request) {
// 校验失败会抛出 MethodArgumentNotValidException
return userService.createUser(request);
}
}
public class UserRequest {
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度3-20")
private String name;
@NotNull(message = "年龄不能为空")
@Min(value = 0, message = "年龄最小0")
@Max(value = 150, message = "年龄最大150")
private Integer age;
@Email(message = "邮箱格式不正确")
private String email;
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
}校验注解
| 注解 | 说明 |
|---|---|
| @NotNull | 不能为 null |
| @NotBlank | 不能为空字符串 |
| @NotEmpty | 不能为空(集合/字符串) |
| @Size | 长度范围 |
| @Min/@Max | 数值范围 |
| 邮箱格式 | |
| @Pattern | 正则表达式 |
| @Past | 过去的时间 |
| @Future | 未来的时间 |
异常处理
@ExceptionHandler
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 处理业务异常
@ExceptionHandler(BusinessException.class)
public Result handleBusinessException(BusinessException e) {
return Result.error(e.getCode(), e.getMessage());
}
// 处理参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleValidException(MethodArgumentNotValidException e) {
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return Result.error(400, message);
}
// 处理通用异常
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
return Result.error(500, "系统异常");
}
}@ControllerAdvice
java
@RestControllerAdvice
public class GlobalExceptionHandler {
// 绑定模型属性,全局共享
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("version", "1.0");
}
// 统一编码
@InitBinder
public void initBinder(WebDataBinder binder) {
binder.setMessageCodesResolver(new DefaultMessageCodesResolver());
}
}拦截器
HandlerInterceptor
java
@Component
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 || !tokenService.validate(token)) {
response.setStatus(401);
return false;
}
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
ModelAndView modelAndView) {
// 请求处理之后,视图渲染之前
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
// 整个请求处理完毕(视图渲染之后)
}
}注册拦截器
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**") // 拦截路径
.excludePathPatterns("/api/login"); // 排除路径
}
}静态资源
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 方式1:默认静态资源位置
// classpath:/static/
// classpath:/public/
// classpath:/resources/
// 方式2:自定义静态资源路径
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
// 方式3:外部目录
registry.addResourceHandler("/files/**")
.addResourceLocations("file:D:/uploads/");
}
}CORS 跨域
@CrossOrigin
java
// 方法级
@RestController
@RequestMapping("/api")
public class UserController {
@CrossOrigin(origins = "http://example.com")
@GetMapping("/user")
public User getUser() { ... }
}
// 类级
@CrossOrigin(origins = "*")
@RestController
@RequestMapping("/api")
public class UserController { }全局配置
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://example.com", "http://example2.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
}文件上传
java
@RestController
public class FileController {
@PostMapping("/upload")
public Result upload(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("文件不能为空");
}
String filename = file.getOriginalFilename();
String path = "D:/uploads/" + filename;
try {
file.transferTo(new File(path));
return Result.success("/uploads/" + filename);
} catch (IOException e) {
return Result.error("上传失败");
}
}
}yaml
# application.yml
spring:
servlet:
multipart:
enabled: true
max-file-size: 10MB
max-request-size: 100MB总结
Spring MVC 核心知识点:
| 知识点 | 面试频率 | 实战重要性 |
|---|---|---|
| 请求处理流程 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| @RequestMapping | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 参数绑定 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 数据校验 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 异常处理 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 拦截器 | ⭐⭐⭐ | ⭐⭐⭐ |
⚠️ 易错点提醒:
- @RestController = @Controller + @ResponseBody
- @PathVariable 获取路径参数,@RequestParam 获取查询参数
- @Valid 和 @Validated 触发校验
- @ExceptionHandler 只处理被 @Controller 抛出的异常
- 文件上传要配置 multipart,否则 MultipartFile 为空