SpringMVC_03_进阶功能

【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 统一异常处理,避免重复代码
相关推荐
咖啡八杯1 小时前
常量与枚举设计规范:HttpStatus 自定义 601 警告码
java·架构·代码规范
ThinkerQAQ_1 小时前
并发编程(六):Atomic 的实现——从 Runtime 到 CPU
java·go·picasso
小白的码BUG之路1 小时前
Docker -- 基本命令
java·docker·eureka
后端LV1 小时前
把限流从「注解」做活:RateLimitKeyResolver 四种真实业务的键玩法
java·后端
imDwAaY1 小时前
Bean的生命周期
java·笔记·后端·学习·spring·dubbo
不停喝水1 小时前
【前端转全栈java速通课】 项目实战④-1 Spring Boot 创建项目-定义接口-请求参数处理-分层规范-依赖注入
java·前端·spring boot
上下求索,莫负韶华2 小时前
Spring全家桶
java·后端·spring
w_zero_one2 小时前
链表(2)
java·数据结构·链表
白杨尚青2 小时前
C++入门篇(十一):string(中)——容量与增删改查:一篇吃透所有常用接口(万字详解)
java·开发语言·c++