SpringMVC 异常处理 & 拦截器

本篇包含 SpringMVC 统一异常处理原理、自定义异常、全局异常解析器;拦截器原理、三大方法、多拦截器执行顺序、拦截器和过滤器对比,配套代码与踩坑说明,适合复习、写博客。

一、SpringMVC 异常处理

1. 核心思路

Controller → Service → Dao 层层调用,异常向上抛出 ,最终交给前端控制器 DispatcherServlet,由异常处理器统一捕获、处理、跳转友好页面。

不推荐在每个 Controller 方法单独写 try-catch,代码冗余,统一异常处理器集中处理更优雅,是企业开发主流方案。

两种处理方式对比:

  1. 局部 try-catch(不推荐):在 Controller 内部捕获异常,自己处理页面跳转;分散、不好维护。
  2. 全局异常处理器(推荐) :实现 HandlerExceptionResolver 接口,所有抛出的异常统一在这里接管。

2. 自定义异常类

业务中我们需要区分【系统内置异常】和【业务自定义异常】,自定义异常方便我们携带业务提示信息:

java

复制代码
package cn.tx.demo3;

public class SysException extends Exception{
    // 异常提示消息
    private String message;

    @Override
    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    // 有参构造,抛出异常时直接传入提示文本
    public SysException(String message) {
        this.message = message;
    }
}

✅ 说明:继承 Exception 是受检异常,也可以继承 RuntimeException 运行时异常,抛出时不需要 throws 声明,项目里两种都常用。

3. 全局异常处理器(HandlerExceptionResolver)

实现接口 HandlerExceptionResolver,重写 resolveException 方法,这个方法在处理器抛出异常时自动触发。

java

复制代码
package cn.tx.demo3;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SysExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception e) {
        // 打印堆栈,方便后台排查日志
        e.printStackTrace();

        SysException exception;
        // 判断:如果是我们自己抛出的业务异常,直接强转
        if(e instanceof SysException){
            exception = (SysException) e;
        }else{
            // 其余未知异常(空指针、除0异常等)统一包装成系统异常,返回通用提示,不要直接暴露原生异常信息给前端
            exception = new SysException("系统正在维护,请联系管理员");
        }

        ModelAndView mv = new ModelAndView();
        // 把异常信息存入域中,供页面获取
        mv.addObject("errorMsg", exception.getMessage());
        // 设置跳转的错误视图
        mv.setViewName("error");
        return mv;
    }
}

4. SpringMVC 配置文件注册异常处理器

xml

复制代码
<!-- 注册全局异常解析器 -->
<bean id="sysExceptionResolver" class="cn.tx.demo3.SysExceptionResolver"/>

5. Controller 模拟抛出异常

java

复制代码
package cn.tx.demo3;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/role")
public class RoleController {

    @RequestMapping("/findAll.do")
    public String findAll() throws SysException {
        System.out.println("执行了...");
        // 模拟异常:除零异常
        int a = 10/0;
        // 也可以手动抛出自定义业务异常
        // throw new SysException("角色数据查询失败!");
        return "suc";
    }
}

6. 错误提示页面 error.jsp

jsp

复制代码
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
    <title>错误提示页面</title>
</head>
<body>
    <h3>出错啦!友好提示页面</h3>
    <p>${errorMsg}</p>
</body>
</html>

💡 补充拓展:SpringMVC 注解版全局异常(@ControllerAdvice + @ExceptionHandler) 现在 SSM、SpringBoot 更常用注解方式,不用实现接口,更加简洁:

java

复制代码
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(SysException.class)
    public ModelAndView handleSysException(SysException e){
        ModelAndView mv = new ModelAndView("error");
        mv.addObject("errorMsg",e.getMessage());
        return mv;
    }
}

二、SpringMVC 拦截器(Interceptor)

1. 概述

拦截器是 SpringMVC 独有的,基于 AOP 思想,专门拦截 Controller 处理器方法,在 Controller 执行前后做预处理、后处理。

拦截器链:多个拦截器按配置顺序串联,依次执行。

✅ 拦截器 vs 过滤器(Filter)高频面试区别

表格

对比项 Filter 过滤器 Interceptor 拦截器
归属 Servlet 规范,JavaWeb 原生 SpringMVC 框架独有组件
拦截范围 /* 可以拦截所有资源:jsp、静态资源、图片、css、Controller 只拦截 Controller 处理器方法,静态资源默认不拦截
执行时机 在 DispatcherServlet之前执行 在 DispatcherServlet 内部,Controller 方法前后执行
使用场景 编码过滤、跨域、全局请求过滤 登录校验、权限判断、日志记录、请求计时

2. 自定义拦截器步骤

  1. 编写类,实现 HandlerInterceptor 接口
  2. 重写需要的三个方法(preHandle /postHandle/afterCompletion)
  3. 在 springmvc.xml 中 <mvc:interceptors> 配置拦截路径、放行路径

3. HandlerInterceptor 三大核心方法(重点)

java

复制代码
package cn.tx.demo4;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class MyInterceptor2 implements HandlerInterceptor {

    /**
     * preHandle:Controller方法【执行之前】执行
     * 返回true:放行,继续执行后续拦截器/Controller
     * 返回false:拦截,终止流程,Controller不会执行
     * 👉 最常用!登录校验、权限判断写在这里
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("拦截器 preHandle 执行...");
        // 示例:拦截后跳转到登录页
        // request.getRequestDispatcher("/login.jsp").forward(request,response);
        return true;
    }

    /**
     * postHandle:Controller方法【执行完毕后】,视图渲染(jsp渲染)之前执行
     * 可以修改 ModelAndView,修改跳转页面、修改域数据
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("拦截器 postHandle 执行...");
    }

    /**
     * afterCompletion:【整个请求全部结束,视图渲染完成之后】执行
     * 资源释放、日志记录,无论中间是否异常,只要preHandle返回true就会执行
     * 此时不能再做页面跳转
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("拦截器 afterCompletion 执行...");
    }
}

Controller 测试代码:

java

复制代码
package cn.tx.demo4;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/dept")
public class DeptController {
    @RequestMapping("/findAll.do")
    public String findAll(){
        System.out.println("controller 业务方法执行了...");
        return "suc";
    }
}

4. springmvc.xml 拦截器配置

xml

复制代码
<!-- 拦截器配置 -->
<mvc:interceptors>
    <mvc:interceptor>
        <!-- mapping:要拦截的路径 -->
        <mvc:mapping path="/dept/**"/>
        <!-- exclude-mapping:放行不拦截的路径,比如登录页面、静态资源 -->
        <mvc:exclude-mapping path="/dept/login.do"/>
        <!-- 拦截器对象 -->
        <bean class="cn.tx.demo4.MyInterceptor2"/>
    </mvc:interceptor>
</mvc:interceptors>

路径语法:/** 代表该路径下所有子请求;/* 只匹配一级路径。

5. 多个拦截器(拦截器链)执行顺序

配置顺序:Interceptor2 → Interceptor3 执行顺序口诀:

preHandle 按配置顺序执行; postHandle、afterCompletion 逆序执行。

完整流程(全部放行,都 return true):

  1. Interceptor2.preHandle
  2. Interceptor3.preHandle
  3. Controller 方法执行
  4. Interceptor3.postHandle
  5. Interceptor2.postHandle
  6. JSP 视图渲染
  7. Interceptor3.afterCompletion
  8. Interceptor2.afterCompletion

⚠️ 特殊情况: 如果第二个拦截器 Interceptor3.preHandle() 返回 false:

  • Interceptor3 的 postHandle、afterCompletion 都不会执行
  • 只有前面已经放行过的拦截器(Interceptor2)的 afterCompletion 会执行

xml

复制代码
<mvc:interceptors>
    <!-- 第一个拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/dept/**"/>
        <bean class="cn.tx.demo4.MyInterceptor2"/>
    </mvc:interceptor>
    <!-- 第二个拦截器 -->
    <mvc:interceptor>
        <mvc:mapping path="/dept/**"/>
        <bean class="cn.tx.demo4.MyInterceptor3"/>
    </mvc:interceptor>
</mvc:interceptors>

6. 拦截器经典业务场景:登录校验示例

java

复制代码
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    Object user = request.getSession().getAttribute("loginUser");
    if(user == null){
        // 未登录,跳转到登录页
        request.getRequestDispatcher("/login.jsp").forward(request,response);
        return false;
    }
    // 已登录,放行
    return true;
}

三、面试总结速记

  1. SpringMVC 异常处理 :异常向上抛,DispatcherServlet 交给HandlerExceptionResolver;自定义异常区分业务错误,统一返回友好页面,避免直接把异常栈给前端。注解版@ControllerAdvice是现在主流写法。
  2. 拦截器三大方法 :
    • preHandle:Controller 之前,返回 true 放行,登录权限校验
    • postHandle:Controller 之后、视图渲染前,可以修改视图数据
    • afterCompletion:页面渲染完成后,释放资源
  3. 多拦截器顺序:pre 顺序,post、after 逆序
  4. Filter 和 Interceptor 区别:Filter 属于 Servlet,拦截全部资源;Interceptor 属于 SpringMVC,只拦截 Controller。
相关推荐
李永奉2 小时前
杰理可视化SDK开发-音频录制功能使用教程
笔记·单片机·嵌入式硬件·mcu·物联网·音视频
ysu_03142 小时前
【C++ 进阶之路】(二)从内联函数到类与对象:深挖内存对齐、this 指针与三大默认成员函数
开发语言·数据结构·c++·笔记·面向对象·this
叼烟扛炮2 小时前
Linux 学习笔记(三十二):线程控制——创建、等待与分离
linux·运维·服务器·笔记·学习·1024程序员节
Nuanyt3 小时前
LeetCode Hot 100 · 双指针 3 题 Java
java·笔记·算法·leetcode·双指针
深圳老胡3 小时前
FDC2212 + STM32G030 液面探测:IIC 读数跳动导致下探深度不一致的排查与解决
笔记·stm32·单片机·代码规范·1024程序员节
程序边界3 小时前
一台数据集成软件的拆解笔记(上):把 KFS 装起来、跑起来,再挑一堆毛病
笔记·原型模式
叼烟扛炮3 小时前
Linux 学习笔记(二十九):进程信号(上)—— 信号捕捉与异步魔法
linux·运维·笔记·学习·1024程序员节
曾庆睿3 小时前
计算机图形学入门笔记_GAMES101_Lecture01-02
笔记·计算机图形学
coolwest3 小时前
某游戏辅助完整破解复盘报告(仅用于本人学习记录)
笔记