【Spring MVC】进阶功能:拦截器、文件上传与全局异常处理
🔑 关键词:Java、Spring MVC、拦截器、HandlerInterceptor、文件上传、全局异常处理、@ControllerAdvice
一、拦截器(HandlerInterceptor)
1.1 什么是拦截器?
拦截器用于在请求到达 Controller 之前 和之后执行预处理或后处理逻辑。
常见场景:登录验证、权限检查、日志记录、请求耗时统计。
1.2 拦截器生命周期
请求进入
↓
preHandle() ──→ 返回 false:请求终止
↓ (返回 true)
Controller 方法执行
↓
postHandle() ──→ 可以修改 ModelAndView
↓
视图渲染
↓
afterCompletion() ──→ 最终回调,可获取异常信息
1.3 代码示例:登录验证拦截器
java
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
Object user = request.getSession().getAttribute("loginUser");
if (user == null) {
response.sendRedirect("/login");
return false; // 拦截
}
return true; // 放行
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
if (ex != null) {
System.out.println("请求异常:" + ex.getMessage());
}
}
}
1.4 注册拦截器
java
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截所有
.excludePathPatterns("/login", "/register", "/static/**");
}
}
1.5 拦截器 vs Filter
| 对比项 | 拦截器(Interceptor) | 过滤器(Filter) |
|---|---|---|
| 规范 | Spring MVC 提供 | Servlet 规范 |
| 作用范围 | 只拦截 Controller 请求 | 拦截所有请求(含静态资源) |
| 使用依赖 | 依赖 Spring 容器 | 不依赖 Spring |
| 粒度 | 可获取 Handler 信息 | 无法获取 Controller 信息 |
二、文件上传
2.1 Controller 处理
java
@Controller
public class FileController {
@PostMapping("/upload")
public String upload(@RequestParam("file") MultipartFile file,
HttpServletRequest request) throws IOException {
if (file.isEmpty()) {
return "上传失败:文件为空";
}
String fileName = file.getOriginalFilename();
String uploadDir = request.getServletContext().getRealPath("/uploads/");
File dir = new File(uploadDir);
if (!dir.exists()) {
dir.mkdirs();
}
file.transferTo(new File(dir, fileName));
return "上传成功:" + fileName;
}
// 多文件上传
@PostMapping("/uploadBatch")
public String uploadBatch(@RequestParam("files") MultipartFile[] files) {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 处理每个文件
}
}
return "批量上传成功";
}
}
配置:Spring Boot 内嵌 Tomcat 默认支持文件上传,只需在 yml 中限制大小:
yaml
spring:
servlet:
multipart:
max-file-size: 10MB
max-request-size: 50MB
三、文件下载
java
@GetMapping("/download")
public ResponseEntity<byte[]> download(String fileName) throws IOException {
String filePath = "/uploads/" + fileName;
byte[] bytes = new FileInputStream(filePath).readAllBytes();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
headers.setContentDispositionFormData("attachment",
URLEncoder.encode(fileName, "UTF-8"));
return new ResponseEntity<>(bytes, headers, HttpStatus.OK);
}
四、全局异常处理
4.1 为什么要全局异常处理?
- 避免每个 Controller 方法都写 try-catch
- 统一异常返回格式
- 区分业务异常和系统异常
4.2 @ControllerAdvice + @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().getFieldErrors().stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(", "));
return Result.error(400, message);
}
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
e.printStackTrace();
return Result.error(500, "服务器内部错误");
}
}
4.3 自定义业务异常
java
public class BusinessException extends RuntimeException {
private int code;
public BusinessException(int code, String message) {
super(message);
this.code = code;
}
public int getCode() { return code; }
}
// 使用
@Service
public class UserService {
public User findById(Long id) {
User user = userMapper.selectById(id);
if (user == null) {
throw new BusinessException(404, "用户不存在");
}
return user;
}
}
4.4 @ExceptionHandler 优先级
| 优先级 | 说明 |
|---|---|
| 1 | 精确匹配:@ExceptionHandler(BusinessException.class) |
| 2 | 父类匹配:@ExceptionHandler(RuntimeException.class) |
| 3 | 兜底匹配:@ExceptionHandler(Exception.class) |
五、SSM 整合配置总结
| 配置文件 | 职责 |
|---|---|
web.xml |
配置 DispatcherServlet、ContextLoaderListener |
spring-context.xml |
Service、DAO、事务、数据源 |
spring-mvc.xml |
Controller 扫描、视图解析器、拦截器 |
mybatis-config.xml |
MyBatis 配置、Mapper 扫描 |
整合要点 :Spring 管理 Service/DAO,Spring MVC 管理 Controller,两个容器通过 ContextLoaderListener 建立父子关系。
六、面试高频问题
Q1:Spring MVC 拦截器的执行顺序?
答:preHandle(Controller 前)→ Controller 方法 → postHandle(Controller 后、视图前)→ 视图渲染 → afterCompletion(最后执行)。如果 preHandle 返回 false,后续方法不执行,但 afterCompletion 仍会执行。
Q2:@ControllerAdvice 的作用?
答:全局增强注解,配合 @ExceptionHandler(全局异常处理)、@InitBinder(参数绑定)、@ModelAttribute(全局模型数据)使用。配合 @ResponseBody 变成 @RestControllerAdvice,直接返回 JSON。
Q3:Spring MVC 如何处理文件上传?
答:通过 MultipartResolver 解析 multipart 请求。Spring 提供 CommonsMultipartResolver(基于 commons-fileupload)和 StandardServletMultipartResolver(基于 Servlet 3.0)。Controller 中用 MultipartFile 接收文件,调用 transferTo() 保存。
七、总结
| 功能 | 核心类/注解 | 要点 |
|---|---|---|
| 拦截器 | HandlerInterceptor | preHandle / postHandle / afterCompletion |
| 文件上传 | MultipartFile | 配置 multipart + transferTo 保存 |
| 文件下载 | ResponseEntity | 设置 Content-Disposition |
| 全局异常 | @ControllerAdvice + @ExceptionHandler | 统一异常处理,避免重复代码 |