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 统一异常处理,避免重复代码
相关推荐
RuoyiOffice21 小时前
SpringBoot+Vue3 凭证生命周期设计:审核、过账、冲销与反过账怎样保持账目一致
java·spring boot·事务·springboot3·财务系统·会计凭证·ruoyi office
2601_9621066421 小时前
HashMap入门|工号-姓名键值存储、两种遍历方式、根据key/value查
java
杨凯凡1 天前
Spring Framework 7 API 版本管理实战:请求头路由、兼容迁移与废弃策略
java·rest api·springframework·spring boot 4·api版本管理
T_Wizard1 天前
同一个问题写了 4 遍:官方 SDK / HTTP / Spring AI / LangChain4j,到底该选哪个
java·ai编程
hai_android1 天前
AudioTrack 深度解析:从原理到实战
android·java·kotlin
斯维赤1 天前
限流、熔断、降级,你真的分得清吗?
java·后端·架构
aiks_yuan1 天前
Java CPU 100% 怎么排查:JDK 11 起 jstack 自带线程 CPU 时间,不用再 top -H 换算十六进制(JDK 8、11、17 实测)
java
Flynn Liang1 天前
AiServices 源码级拆解:一个接口如何变成 RAG Agent
java·langchain·源码分析·rag·langchain4j·aiservices
Flynn Liang1 天前
LangChain4j 1.23 架构全景:Java 大模型应用的 6 层抽象
java·langchain·大模型·rag·ai agent·langchain4j
聪明的笨猪猪1 天前
实际工程问题:AI生成Redis分布式锁的原子性缺陷导致库存超卖
java·经验分享·面试·ai编程