SpringBoot 接口规范:统一返回、异常处理与拦截器详解

一、拦截器的核心定义

拦截器(Interceptor)是Spring MVC 框架中用于拦截并处理请求的组件,本质是一种 AOP(面向切面编程)思想的实现,能在请求到达目标处理器(如 Controller 的接口方法)之前、之后,或请求完成后执行自定义逻辑,还可根据条件阻断请求流程。

简单来说:拦截器就像请求的 "关卡守卫",所有符合规则的请求都会经过这道关卡,你可以在关卡处做三件核心事:

  1. 「前置校验」:请求到达接口前检查(如登录状态、权限、参数合法性);
  2. 「后置处理」:接口执行完后加工结果(如统一封装返回格式、记录日志);
  3. 「阻断请求」:校验不通过时直接拒绝请求,不让其到达接口。
拦截器的核心特征
  1. 针对性拦截 :可精准指定拦截 / 放行的请求路径(如只拦截/api/**,放行/login);
  2. 可插拔:通过配置注册 / 移除,无需修改业务代码,符合 "开闭原则";
  3. 生命周期明确
    • preHandle:接口执行前(核心,决定是否放行);
    • postHandle:接口执行后、响应返回前;
    • afterCompletion:响应返回后(如清理资源)。
拦截器的典型应用场景
  1. 登录状态校验(如校验 Session/Token,未登录则拦截并返回登录提示);
  2. 权限控制(如判断用户是否有操作某接口的权限);
  3. 接口耗时统计(前置记录开始时间,后置计算耗时);
  4. 统一日志记录(记录请求参数、响应结果);
  5. 参数统一校验 / 格式化(如过滤非法参数)。
通俗类比

把请求比作 "快递配送":

  • 拦截器就是快递站的 "分拣员";
  • preHandle:分拣前检查(如是否禁运品、地址是否合规),不合规直接退回(阻断),合规则继续配送;
  • postHandle:配送完成后,登记 "已签收" 状态;
  • afterCompletion:整个配送流程结束后,归档快递单。

二、拦截器的核心作用

解决 "逐个接口校验 Session" 的繁琐问题,实现统一拦截请求并校验 Session

  • 替代 "修改每个接口逻辑、返回结果、前端代码" 的重复操作;
  • 可在请求前后执行预设逻辑(如登录校验),也能直接阻断请求。

三、拦截器的使用步骤

  1. 定义拦截器 实现HandlerInterceptor接口,重写 3 个方法:

    • preHandle:目标方法执行 执行,返回true则继续请求,返回false则阻断请求;
    • postHandle:目标方法执行执行;
    • afterCompletion:视图渲染完成后执行(后端开发中较少涉及)。

2.注册配置拦截器 实现WebMvcConfigurer接口,通过addInterceptors方法注册拦截器,并指定拦截的请求路径(如/**表示拦截所有请求)。

四、拦截器详解

1.拦截路径配置

拦截路径用于指定拦截器对哪些请求生效,通过注册配置类的两个方法实现:

  • addPathPatterns():指定需要拦截的请求路径;
  • excludePathPatterns():指定需要排除(不拦截)的请求路径。
常见路径规则及含义
拦截路径 含义 匹配示例
/* 一级路径 匹配/user/book,不匹配/user/login
/** 任意级路径 匹配/user/user/login/user/reg
/book/* /book下的一级路径 匹配/book/addBook,不匹配/book/addBook/1
/book/** /book下的任意级路径 匹配/book/book/addBook/book/addBook/2

2.拦截器执行流程

拦截器会嵌入请求的处理流程中,核心执行顺序为:

  1. 请求到达后 :先执行拦截器的preHandle()方法
    • 返回true:放行请求,继续执行 Controller 及后续业务逻辑;
    • 返回false:阻断请求,不再执行后续流程。
  2. Controller 方法执行完成后 :执行拦截器的postHandle()方法。
  3. 视图渲染完成 / 响应返回后 :执行拦截器的afterCompletion()方法。

有了拦截器之后,会在调⽤Controller之前进⾏相应的业务处理,执⾏的流程如下图

流程对比(有无拦截器)
  • 无拦截器:用户请求 → Controller → Service → Mapper → 数据库。
  • 有拦截器 :用户请求 → 拦截器preHandle() → Controller → Service → Mapper → 数据库 → Controller 执行完成 → 拦截器postHandle() → 响应返回 → 拦截器afterCompletion()

4.3登录校验

4.3.1定义拦截器

request.getSession(false)false核心开关 ,作用是:仅获取已有会话,绝不主动创建新会话------ 这是保证登录拦截逻辑严谨性 的关键,和不加 false(即 getSession() 无参)的行为有本质区别。

为什么登录拦截必须用 getSession(false)

拦截器逻辑,核心目标是「校验用户是否已登录 」,而非「为未登录用户创建会话」,如果不用 false,会导致两个严重问题:

问题 1:无意义的空会话被创建,浪费服务器资源

如果用户未登录(没带 JSESSIONID),调用 getSession()(无参)会自动生成新会话,此时:

  • session != null 永远为 true(因为刚创建了新会话);
  • 只能靠 session.getAttribute(USER_KEY) 校验登录状态;
  • 但服务器会为每个未登录请求创建空会话,日积月累会占用大量内存(尤其高并发场景)。
问题 2:拦截逻辑看似没问题,但语义和性能都不严谨

举个对比例子:

这段代码逻辑上能拦截未登录用户,但每一个未登录请求都会生成一个空会话------ 相当于 "为了检查有没有钥匙,先给每个陌生人配一把空锁",完全没必要。

getSession(false) 的逻辑是:

"先看看客户端有没有带有效的「锁」,有锁再查里面有没有「钥匙」(用户信息);没锁直接返回 null,不新建任何东西。"

  • 当用户未登录且无会话 → session = null → 拦截;
  • 当用户未登录但有空会话 → session != nullSESSION_USER_KEY ``= null → 拦截;
  • 当用户已登录 → 两者都满足 → 放行。
401 状态码的含义
  • 英文定义:表示请求需要身份验证,但未提供验证信息,或提供的信息验证失败;若已包含授权凭据,则表示这些凭据不被接受。
  • 中文解释:未经过认证,即身份验证是必需的,但请求未提供验证信息、验证失败,或提供的授权凭据不被认可。
适用场景

在 Web 开发中,401 通常用于:

  1. 用户未登录时访问需要权限的接口;
  2. 登录后的 Token/Session 失效或不合法;
  3. 提供的账号密码、API 密钥等授权信息错误。
与类似状态码的区分
  • 401(未授权) :聚焦 "身份验证缺失 / 失败",需用户提供有效凭据;
  • 403(禁止访问) :身份验证已通过,但用户无权限访问资源。
4.3.2注册配置拦截器
DispatcherServlet 的核心定位

DispatcherServlet是 Spring MVC 的 "前端控制器",所有请求都会先进入该组件,由它调度后续流程(包括拦截器、Controller 的执行)。

请求处理流程(与拦截器的关系)

请求到达后,DispatcherServlet执行doDispatch方法,流程为:

  1. 先执行拦截器的preHandle()方法;
  2. preHandle()返回true,继续执行 Controller 的方法;
  3. Controller 方法执行完成后,执行拦截器的postHandle()afterCompletion()方法;
  4. 最终由DispatcherServlet将响应返回给浏览器。
DispatcherServlet 的初始化过程

初始化逻辑主要在父类HttpServletBeaninit()方法中,核心步骤:

  1. 加载web.xmlDispatcherServlet的配置参数;
  2. 调用initServletBean()(由子类FrameworkServlet实现),创建WebApplicationContext容器;
  3. 加载 Spring MVC 配置文件中的 Bean 到容器,并将容器绑定到ServletContext
  4. 初始化完成后输出日志(对应启动日志中 "Initializing Spring DispatcherServlet" 的内容)。

五、适配器模式的核心定义

适配器模式(又称包装器模式)是一种结构型设计模式,作用是将一个类的接口转换为客户端期望的另一个接口,解决两个不兼容接口的协作问题。简单理解:当目标类无法直接被调用时,通过一个 "适配器" 类进行包装,让原本不兼容的接口可以协同工作。

5.1适配器模式的核心角色
角色 含义
Target 客户端期望直接使用的目标接口(抽象类 / 接口)
Adaptee 需要被适配的适配者(与 Target 接口不兼容的类)
Adapter 适配器类(模式核心),通过继承 / 引用 Adaptee,将其转换为 Target 接口
Client 使用适配器的对象(调用 Target 接口)
5.2实现示例(SLF4J 与 Log4j 的适配)

SLF4J 是日志门面接口,Log4j 是具体日志实现,二者接口不兼容,通过适配器实现协作:

  1. TargetSLF4jApi(定义log方法);
  2. AdapteeLog4j(提供log4jLog方法);
  3. AdapterSLF4jLog4jAdapter(实现SLF4jApi,内部引用Log4j,将log方法转换为log4jLog调用);
  4. Client :通过SLF4jApi调用日志功能,无需直接依赖 Log4j。
5.3应用场景

适配器模式是一种 "补偿模式",主要用于:

  • 已有系统中接口不兼容的改造(如版本升级、替换依赖框架);
  • 复用现有代码,同时适配新的接口规范,避免大规模修改原有逻辑。
5.4在 Spring MVC 中的体现

Spring MVC 中的HandlerAdapter就是适配器模式的典型应用:它将不同类型的处理器(如 Controller、HttpRequestHandler)适配为统一的接口,让DispatcherServlet可以无差别调用。

六、统⼀数据返回格式

为什么要做 "统一数据返回格式"?

后端接口返回的数据(比如成功 / 失败、业务数据、错误信息)如果格式不统一,前端要写大量冗余代码来适配不同格式。所以需要把所有接口的返回结果封装成固定结构(包含状态、错误信息、业务数据)。

1.核心实现步骤

1. 定义统一返回实体类

先写一个Result<T>类,作为所有接口的返回 "容器",包含 3 个核心字段:

复制代码
@Data
public class Result<T> {
    // 状态(比如success/fail,或数字状态码)
    private String status;
    // 错误信息(失败时返回)
    private String errorMessage;
    // 业务数据(成功时返回)
    private T data;

    // 快捷构造方法:成功时调用
    public static <T> Result<T> success(T data) {
        Result<T> result = new Result<>();
        result.setStatus("success");
        result.setData(data);
        return result;
    }

    // 快捷构造方法:失败时调用
    public static <T> Result<T> fail(String errorMessage) {
        Result<T> result = new Result<>();
        result.setStatus("fail");
        result.setErrorMessage(errorMessage);
        return result;
    }
}
2. 早期手动封装(缺点:每个接口都要写)

最开始是在每个 Controller 方法里手动调用Result.success()/Result.fail(),比如:

复制代码
@RequestMapping("/getListByPage")
public Result getListByPage(PageRequest pageRequest) {
    PageResult pageResult = bookService.getBookListByPage(pageRequest);
    // 手动封装成Result返回
    return Result.success(pageResult);
}

→ 缺点:每个接口都要写Result.success(),代码冗余。

3. 进阶:用@ControllerAdvice + ResponseBodyAdvice自动封装

为了不用每个接口手动写封装逻辑 ,SpringBoot 提供了ResponseBodyAdvice接口,配合@ControllerAdvice实现 "全局拦截 + 自动封装"。

实现步骤:① 写一个类,加@ControllerAdvice注解,实现ResponseBodyAdvice接口② 重写 2 个方法:

  • supports():判断是否要执行封装(返回true表示所有接口都拦截)
  • beforeBodyWrite():在返回数据给前端前,自动把数据封装成Result

代码示例:

复制代码
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice // 表示这是一个全局控制器通知类
public class ResponseAdvice implements ResponseBodyAdvice {

    // 判断是否要执行beforeBodyWrite(true=执行)
    @Override
    public boolean supports(MethodParameter returnType, Class converterType) {
        return true; // 所有接口都拦截
    }

    // 对返回数据进行自动封装
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, 
                                  MediaType selectedContentType, Class selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        // 把原本的返回数据body,封装成Result.success(body)
        return Result.success(body);
    }
}
4. 效果对比(测试接口)

/queryBookById?bookId=1为例:

  • 封装前:返回的是原始业务对象(格式不统一)

    {
    "id": 1,
    "bookName": "活着",
    "author": "余华",
    "count": 20
    }

  • 封装后 :自动变成Result格式(包含状态、业务数据)

    {
    "status": "success",
    "errorMessage": null,
    "data": {
    "id": 1,
    "bookName": "活着",
    "author": "余华",
    "count": 20
    }
    }

补充:supports()的灵活用法

如果想指定某些接口不封装 ,可以在supports()里加判断(比如根据类名 / 方法名过滤):

复制代码
@Override
public boolean supports(MethodParameter returnType, Class converterType) {
    // 获取当前接口所在的类
    Class<?> declaringClass = returnType.getMethod().getDeclaringClass();
    // 获取当前接口的方法名
    Method method = returnType.getMethod();
    
    // 比如:UserController下的login方法不封装
    return !(declaringClass == UserController.class && method.getName().equals("login"));
}
总结
  1. 先定义Result<T>作为统一返回的 "容器"(包含状态、错误信息、业务数据);
  2. 写一个类(比如ResponseAdvice),加**@ControllerAdvice注解** + 实现**ResponseBodyAdvice接口,重写supports()** (返回true表示拦截所有接口)和beforeBodyWrite()(在这里自动把返回值封装成Result)。

这样所有 Controller 方法执行完,Spring 会自动调用这个类的beforeBodyWrite,把返回值包装成Result格式返回给前端,省掉了每个接口手动写封装的冗余代码

复制代码
src/main/java/com/你的项目名/
├── common/                // 公共工具/通用类(必建)
│   ├── result/            // 统一返回相关(专门放Result和ResponseAdvice)
│   │   ├── Result.java    // 统一返回容器
│   │   └── ResponseAdvice.java // 全局自动封装类
├── controller/            // 你的业务Controller(比如BookController)
├── service/               // 业务Service
└── Application.java       // SpringBoot启动类

核心:Result.javaResponseAdvice.java放在common.result包下即可,SpringBoot 会自动扫描@ControllerAdvice@Component注解。

步骤 1:定义统一返回容器Result<T>:

步骤 2:写全局自动封装类ResponseAdvice:

步骤 3:直接写 Controller 接口(不用手动封装):

2.SpringBoot 统一数据返回时 "String 类型接口报错" 的问题、原因和解决方案

1. 问题现象

测试/book/updateBook接口时,数据库操作成功,但接口返回 500 错误 ,日志报错Result cannot be cast to java.lang.String(Result 类型不能转成 String)。同时测试发现:只有返回值是String类型的接口会报错(比如public String t1()),返回 Boolean/Integer 的接口正常。

2. 解决方案

修改ResponseAdvicebeforeBodyWrite方法,单独处理 String 类型的返回值 :用 Jackson 将Result.success(body)序列化成 String,再返回(避免类型转换错误)。核心代码:

复制代码
// 注入Jackson的ObjectMapper
private static ObjectMapper mapper = new ObjectMapper();

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, ...) {
    // 单独处理String类型
    if (body instanceof String) {
        // 把Result对象序列化成String
        return mapper.writeValueAsString(Result.success(body));
    }
    // 其他类型正常封装
    return Result.success(body);
}

多测试⼏种不同的返回结果,发现只有返回结果为String类型时才有这种错误发⽣.

这个错误的根源是 SpringMVC 的HttpMessageConverter执行顺序,具体流程:

1. SpringMVC 的HttpMessageConverter

SpringMVC 会注册多个HttpMessageConverter(用于将 Java 对象转成 HTTP 响应体),默认顺序是:ByteArrayHttpMessageConverterStringHttpMessageConverterSourceHttpMessageConverterAllEncompassingFormHttpMessageConverter(后面会加 Jackson 的MappingJackson2HttpMessageConverter)。

2. 不同返回类型的 Converter 选择逻辑

Spring 会按顺序遍历 Converter 链,选第一个能处理当前返回类型的 Converter:

  • 当返回值是非 String 类型 (比如 Boolean/Integer/ 对象):会跳过前面的StringHttpMessageConverter,用后面的MappingJackson2HttpMessageConverter(能处理对象转 JSON),所以Result封装后能正常转 JSON。

  • 当返回值是String 类型 :会优先匹配到StringHttpMessageConverter(因为它在链的前面),而StringHttpMessageConverter只能处理String类型的返回值。

3. 报错的关键环节

当我们用ResponseAdvice把 String 类型的返回值封装成 Result 对象 后,StringHttpMessageConverter会尝试处理这个 Result 对象,但它的addDefaultHeaders方法要求参数是String类型,此时传入的是 Result 类型,就会抛出类型转换异常

总结

这个问题是SpringMVC 的 Converter 执行顺序 + String 类型的特殊处理 导致的,解决方案是对 String 类型单独做 Jackson 序列化 ,避免StringHttpMessageConverter处理 Result 对象。

3.拦截 / 排除接口的固定模板

模板 1:只拦截指定的 Controller(常用)
模板 2:排除指定的接口(常用)
模板 3:只拦截指定前缀的方法
模板 4:排除所有 String 返回值的接口
总结
  1. 获取接口信息的写法是固定的 :不管是拦截还是排除,都是通过returnType拿类名 / 方法名,语法完全不变;
  2. 条件判断的逻辑是固定的return true= 拦截,return false= 不拦截,只需调整判断条件;
  3. 实际开发中几乎不用改 :99% 的场景直接用return true拦截所有接口即可,只有特殊接口(比如第三方对接的接口)才需要排除。
  4. supports/beforeBodyWrite的参数列表完全固定,只能改方法体;
  5. 拦截 / 排除接口的核心是通过MethodParameter获取接口信息,获取方式固定,只需调整判断条件;

七、SpringBoot 中 "统一异常处理" 的实现逻辑

1.统一异常处理的核心:@ControllerAdvice + @ExceptionHandler

这两个注解配合,能实现全局捕获异常 + 统一返回格式(避免接口直接抛 "裸异常")。

  • @ControllerAdvice:全局控制器通知类(和之前统一数据返回用的是同一个注解,作用是 "全局拦截");
  • @ExceptionHandler:异常处理器,声明 "当出现某个异常时,执行这个方法"。

2.基础实现(捕获所有异常)

先看最基础的代码:

逻辑说明:

当你的 Controller 接口抛出任何 Exception 类型的异常 (比如空指针、除以 0),Spring 会自动调用这个handler方法,把异常信息封装成Result格式返回给前端,而不是直接返回 500 错误 + 堆栈信息。

3.进阶:针对不同异常,返回不同信息

如果想对特定异常做定制化提示 (比如空指针、算术异常分开处理),可以写多个@ExceptionHandler方法,指定不同的异常类型:

5.异常匹配规则

当有多个@ExceptionHandler时,Spring 会优先匹配 "最具体的异常类型" :比如抛出NullPointerException,会先匹配@ExceptionHandler(NullPointerException.class),而不是兜底的@ExceptionHandler(Exception.class)

6.和统一数据返回的关系

统一异常处理和统一数据返回,都是基于@ControllerAdvice实现的:

  • 统一数据返回:用ResponseBodyAdvice拦截正常返回的结果,封装成 Result;
  • 统一异常处理:用@ExceptionHandler拦截抛出的异常,封装成 Result。

最终不管是正常返回还是异常返回,前端拿到的都是相同格式的 Result 对象,不用额外适配。

7.为什么不用在通知类里 "加进去"?

统一数据返回的ResponseAdvice是处理正常返回的结果 ,而统一异常处理的ErrorAdvice是处理异常情况,两者是 "分工协作" 的:

  • ResponseAdvice:拦截正常返回的业务数据,自动封装Result.success()
  • ErrorAdvice:拦截抛出的异常,自动封装Result.fail()

这两个类都是基于@ControllerAdvice的全局通知,覆盖了 "正常返回" 和 "异常返回" 所有场景,所以不需要在一个通知类里混写,分开写更清晰,也符合 "单一职责" 原则

8.统一异常处理完整代码

1.核心目标

  1. 所有接口正常返回 :自动封装成 Result.success(业务数据),不用手动写;
  2. 所有接口抛出异常 :自动封装成 Result.fail(状态码 + 错误提示),不用手动写;
  3. 异常分类处理:自定义业务异常(比如用户名重复)和系统异常(比如空指针)分开提示,格式统一。

2.统一异常处理完整代码

2.1自定义业务异常类(BusinessException.java)
2. 2全局异常处理类(ErrorAdvice.java)
2.3 配套的统一返回容器(Result.java,必须有)

3.使用示例(业务接口怎么写)

4.核心执行逻辑

场景 1:调用 /user/add?username=张三
  1. 接口抛出 BusinessException("用户名【张三】已存在...")
  2. 全局异常类 ErrorAdvice 匹配到 @ExceptionHandler(BusinessException.class)
  3. 执行 handleBusinessException 方法,返回 Result.fail(400, "业务异常:用户名【张三】已存在...")
  4. 前端最终拿到:
场景 2:调用 /test/null
  1. 接口抛出 NullPointerException
  2. 全局异常类匹配到 @ExceptionHandler(NullPointerException.class)
  3. 执行 handleNullPointException 方法,返回 Result.fail(500, "空指针异常:null")
  4. 前端拿到统一格式的异常结果。
场景 3:调用未明确匹配的异常(比如 IO 异常)
  1. 接口抛出 IOException
  2. 全局异常类没有专门匹配的方法,走兜底的 @ExceptionHandler(Exception.class)
  3. 执行 handleAllException 方法,返回 Result.fail(500, "系统异常:xxx")

5.关键总结

  1. 异常不用手动封装 :业务接口只需要 throw new 异常类("提示文案"),全局异常类自动封装成 Result.fail()

  2. 异常分类处理:自定义业务异常(400)和系统异常(500)分开,前端能精准判断错误类型;

  3. 格式完全统一 :不管是正常返回(Result.success)还是异常返回(Result.fail),前端拿到的都是同一种 JSON 格式,不用适配多种返回结构。

    1. 写1个父类异常(BusinessException)→ 所有业务异常的"总父类";
    2. 写N个子类异常(比如UsernameDuplicateException)→ 细分具体业务场景;
    3. 写1个全局异常类(ErrorAdvice)→
      • 手动写N个细分异常的处理方法(精细化提示,比如"用户管理异常:xxx");
      • 必须写1个父类兜底方法 → 防止漏写子类时,异常被错误归类为"系统异常"。
  4. 细分异常需要创建子类(比如 UsernameDuplicateException),继承父类 BusinessException;

  5. 全局异常类里要手动写每个子类异常的处理方法return Result.fail (xxx));

  6. 父类兜底方法是为了 "漏写子类异常时兜底"。

  • 自定义异常类(父类 + 子类)里只定义 "异常类型"不写处理逻辑
  • 异常的处理逻辑(return Result.fail (...))全部写在全局异常类(ErrorAdvice) 里,自定义异常类只负责 "标识异常类型"。

最简实践

如果觉得子类太多麻烦,可以先不用写子类,只做两步:

  1. 写 1 个父类异常 BusinessException;
  2. 全局异常类里只写父类兜底方法。

代码示例:

throw new BusinessException("用户名【张三】已存在")

这个字符串参数的传递路径是 "接口抛异常时传入 → 自定义异常类接收 → 全局异常类取出使用",全程就 3 步:

步骤 1:接口里 throw new 时传入参数
复制代码
// 你手动写的字符串,就是传给自定义异常构造方法的参数
throw new BusinessException("用户名【张三】已存在");
步骤 2:自定义异常类的构造方法接收参数
复制代码
public class BusinessException extends RuntimeException {
    // 构造方法接收这个字符串,并用 super(message) 传给父类 RuntimeException
    public BusinessException(String message) {
        super(message); // 关键:把字符串存到异常对象里
    }
}
步骤 3:全局异常类里取出这个参数
复制代码
@ExceptionHandler(BusinessException.class)
public Result<?> handleBusinessException(BusinessException e) {
    // e.getMessage() 就是取出你传入的 "用户名【张三】已存在"
    return Result.fail(400, "业务异常:" + e.getMessage());
}

二、必须写 throw new 吗?分两种情况:

情况 1:你想主动抛业务异常(比如用户名重复、参数错误)→ 必须写 throw new

只有通过 throw new 异常类("提示文案"),才能主动触发异常流程,让全局异常类捕获并处理。 示例(主动抛业务异常):

情况 2:系统自动抛的异常(比如空指针、除以 0)→ 不用你写 throw new

这类异常是代码运行时自动触发的,你不用手动写 throw new,全局异常类也能捕获。示例(系统自动抛异常):

  • 参数传递throw new 异常类("文案") 里的字符串,最终会通过 e.getMessage() 被全局异常类取出,拼到返回提示里;
  • throw new 的作用:主动抛业务异常时必须写(比如校验不通过),系统自动抛的异常不用写;
  • 兜底逻辑:抛了自定义异常但没写对应处理方法 → 触发父类兜底;没抛自定义异常(系统异常)→ 触发系统兜底。
场景 是否要写 throw new 触发哪个异常处理方法
主动抛自定义细分异常(比如 UsernameDuplicateException) 全局类里对应的子类处理方法
主动抛自定义父类异常(BusinessException) 全局类里的父类兜底方法
没抛自定义异常,系统自动抛异常(比如空指针) 全局类里的系统异常兜底方法(Exception.class)
漏写自定义细分异常的处理方法 是(抛子类) 全局类里的父类兜底方法

e.getMessage() 拿到的内容,主动抛异常时就是你写的字符串 (比如"用户名【张三】已存在"),不主动抛时就是系统默认的异常信息 (比如空指针的"null"、除以 0 的"/ by zero")。

相关推荐
计算机学姐21 分钟前
基于SpringBoot的校园社团管理系统
java·vue.js·spring boot·后端·spring·信息可视化·推荐算法
Coder_Boy_27 分钟前
基于SpringAI的在线考试系统-企业级教育考试系统核心架构(完善版)
开发语言·人工智能·spring boot·python·架构·领域驱动
java1234_小锋28 分钟前
Java高频面试题:SpringBoot如何自定义Starter?
java·spring boot·面试
落霞的思绪29 分钟前
Spring AI Alibaba 集成 Redis 向量数据库实现 RAG 与记忆功能
java·spring·rag·springai
键盘帽子29 分钟前
长连接中异步任务的同步等待陷阱:一次主线程阻塞的排查与修复
java·websocket·java-ee·web
你刷碗30 分钟前
基于S32K144 CESc生成随机数
android·java·数据库
hssfscv33 分钟前
Javaweb学习笔记——后端实战8 springboot原理
笔记·后端·学习
咚为33 分钟前
Rust tokio:Task ≠ Thread:Tokio 调度模型中的“假并发”与真实代价
开发语言·后端·rust
灰子学技术38 分钟前
性能分析工具比较pprof、perf、valgrind、asan
java·开发语言
木井巳40 分钟前
【多线程】单例模式
java·单例模式·java-ee