一、拦截器的核心定义
拦截器(Interceptor)是Spring MVC 框架中用于拦截并处理请求的组件,本质是一种 AOP(面向切面编程)思想的实现,能在请求到达目标处理器(如 Controller 的接口方法)之前、之后,或请求完成后执行自定义逻辑,还可根据条件阻断请求流程。
简单来说:拦截器就像请求的 "关卡守卫",所有符合规则的请求都会经过这道关卡,你可以在关卡处做三件核心事:
- 「前置校验」:请求到达接口前检查(如登录状态、权限、参数合法性);
- 「后置处理」:接口执行完后加工结果(如统一封装返回格式、记录日志);
- 「阻断请求」:校验不通过时直接拒绝请求,不让其到达接口。
拦截器的核心特征
- 针对性拦截 :可精准指定拦截 / 放行的请求路径(如只拦截
/api/**,放行/login); - 可插拔:通过配置注册 / 移除,无需修改业务代码,符合 "开闭原则";
- 生命周期明确 :
preHandle:接口执行前(核心,决定是否放行);postHandle:接口执行后、响应返回前;afterCompletion:响应返回后(如清理资源)。
拦截器的典型应用场景
- 登录状态校验(如校验 Session/Token,未登录则拦截并返回登录提示);
- 权限控制(如判断用户是否有操作某接口的权限);
- 接口耗时统计(前置记录开始时间,后置计算耗时);
- 统一日志记录(记录请求参数、响应结果);
- 参数统一校验 / 格式化(如过滤非法参数)。
通俗类比
把请求比作 "快递配送":
- 拦截器就是快递站的 "分拣员";
preHandle:分拣前检查(如是否禁运品、地址是否合规),不合规直接退回(阻断),合规则继续配送;postHandle:配送完成后,登记 "已签收" 状态;afterCompletion:整个配送流程结束后,归档快递单。
二、拦截器的核心作用
解决 "逐个接口校验 Session" 的繁琐问题,实现统一拦截请求并校验 Session:
- 替代 "修改每个接口逻辑、返回结果、前端代码" 的重复操作;
- 可在请求前后执行预设逻辑(如登录校验),也能直接阻断请求。

三、拦截器的使用步骤
-
定义拦截器 实现
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.拦截器执行流程
拦截器会嵌入请求的处理流程中,核心执行顺序为:
- 请求到达后 :先执行拦截器的
preHandle()方法- 返回
true:放行请求,继续执行 Controller 及后续业务逻辑; - 返回
false:阻断请求,不再执行后续流程。
- 返回
- Controller 方法执行完成后 :执行拦截器的
postHandle()方法。 - 视图渲染完成 / 响应返回后 :执行拦截器的
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 != null但SESSION_USER_KEY ``= null→ 拦截; - 当用户已登录 → 两者都满足 → 放行。
401 状态码的含义
- 英文定义:表示请求需要身份验证,但未提供验证信息,或提供的信息验证失败;若已包含授权凭据,则表示这些凭据不被接受。
- 中文解释:未经过认证,即身份验证是必需的,但请求未提供验证信息、验证失败,或提供的授权凭据不被认可。
适用场景
在 Web 开发中,401 通常用于:
- 用户未登录时访问需要权限的接口;
- 登录后的 Token/Session 失效或不合法;
- 提供的账号密码、API 密钥等授权信息错误。
与类似状态码的区分
- 401(未授权) :聚焦 "身份验证缺失 / 失败",需用户提供有效凭据;
- 403(禁止访问) :身份验证已通过,但用户无权限访问资源。
4.3.2注册配置拦截器

DispatcherServlet 的核心定位
DispatcherServlet是 Spring MVC 的 "前端控制器",所有请求都会先进入该组件,由它调度后续流程(包括拦截器、Controller 的执行)。
请求处理流程(与拦截器的关系)
请求到达后,DispatcherServlet执行doDispatch方法,流程为:
- 先执行拦截器的
preHandle()方法; - 若
preHandle()返回true,继续执行 Controller 的方法; - Controller 方法执行完成后,执行拦截器的
postHandle()和afterCompletion()方法; - 最终由
DispatcherServlet将响应返回给浏览器。
DispatcherServlet 的初始化过程
初始化逻辑主要在父类HttpServletBean的init()方法中,核心步骤:
- 加载
web.xml中DispatcherServlet的配置参数; - 调用
initServletBean()(由子类FrameworkServlet实现),创建WebApplicationContext容器; - 加载 Spring MVC 配置文件中的 Bean 到容器,并将容器绑定到
ServletContext; - 初始化完成后输出日志(对应启动日志中 "Initializing Spring DispatcherServlet" 的内容)。

五、适配器模式的核心定义
适配器模式(又称包装器模式)是一种结构型设计模式,作用是将一个类的接口转换为客户端期望的另一个接口,解决两个不兼容接口的协作问题。简单理解:当目标类无法直接被调用时,通过一个 "适配器" 类进行包装,让原本不兼容的接口可以协同工作。
5.1适配器模式的核心角色
| 角色 | 含义 |
|---|---|
| Target | 客户端期望直接使用的目标接口(抽象类 / 接口) |
| Adaptee | 需要被适配的适配者(与 Target 接口不兼容的类) |
| Adapter | 适配器类(模式核心),通过继承 / 引用 Adaptee,将其转换为 Target 接口 |
| Client | 使用适配器的对象(调用 Target 接口) |


5.2实现示例(SLF4J 与 Log4j 的适配)
SLF4J 是日志门面接口,Log4j 是具体日志实现,二者接口不兼容,通过适配器实现协作:
- Target :
SLF4jApi(定义log方法); - Adaptee :
Log4j(提供log4jLog方法); - Adapter :
SLF4jLog4jAdapter(实现SLF4jApi,内部引用Log4j,将log方法转换为log4jLog调用); - 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"));
}
总结
- 先定义
Result<T>作为统一返回的 "容器"(包含状态、错误信息、业务数据); - 写一个类(比如
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.java和ResponseAdvice.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. 解决方案
修改ResponseAdvice的beforeBodyWrite方法,单独处理 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 响应体),默认顺序是:ByteArrayHttpMessageConverter → StringHttpMessageConverter → SourceHttpMessageConverter → AllEncompassingFormHttpMessageConverter(后面会加 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 返回值的接口

总结
- 获取接口信息的写法是固定的 :不管是拦截还是排除,都是通过
returnType拿类名 / 方法名,语法完全不变; - 条件判断的逻辑是固定的 :
return true= 拦截,return false= 不拦截,只需调整判断条件; - 实际开发中几乎不用改 :99% 的场景直接用
return true拦截所有接口即可,只有特殊接口(比如第三方对接的接口)才需要排除。 supports/beforeBodyWrite的参数列表完全固定,只能改方法体;- 拦截 / 排除接口的核心是通过
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.核心目标
- 所有接口正常返回 :自动封装成
Result.success(业务数据),不用手动写; - 所有接口抛出异常 :自动封装成
Result.fail(状态码 + 错误提示),不用手动写; - 异常分类处理:自定义业务异常(比如用户名重复)和系统异常(比如空指针)分开提示,格式统一。
2.统一异常处理完整代码
2.1自定义业务异常类(BusinessException.java)

2. 2全局异常处理类(ErrorAdvice.java)

2.3 配套的统一返回容器(Result.java,必须有)

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

4.核心执行逻辑
场景 1:调用 /user/add?username=张三
- 接口抛出
BusinessException("用户名【张三】已存在..."); - 全局异常类
ErrorAdvice匹配到@ExceptionHandler(BusinessException.class); - 执行
handleBusinessException方法,返回Result.fail(400, "业务异常:用户名【张三】已存在..."); - 前端最终拿到:

场景 2:调用 /test/null
- 接口抛出
NullPointerException; - 全局异常类匹配到
@ExceptionHandler(NullPointerException.class); - 执行
handleNullPointException方法,返回Result.fail(500, "空指针异常:null"); - 前端拿到统一格式的异常结果。
场景 3:调用未明确匹配的异常(比如 IO 异常)
- 接口抛出
IOException; - 全局异常类没有专门匹配的方法,走兜底的
@ExceptionHandler(Exception.class); - 执行
handleAllException方法,返回Result.fail(500, "系统异常:xxx")。
5.关键总结
-
异常不用手动封装 :业务接口只需要
throw new 异常类("提示文案"),全局异常类自动封装成Result.fail(); -
异常分类处理:自定义业务异常(400)和系统异常(500)分开,前端能精准判断错误类型;
-
格式完全统一 :不管是正常返回(
Result.success)还是异常返回(Result.fail),前端拿到的都是同一种 JSON 格式,不用适配多种返回结构。- 写1个父类异常(BusinessException)→ 所有业务异常的"总父类";
- 写N个子类异常(比如UsernameDuplicateException)→ 细分具体业务场景;
- 写1个全局异常类(ErrorAdvice)→
- 手动写N个细分异常的处理方法(精细化提示,比如"用户管理异常:xxx");
- 必须写1个父类兜底方法 → 防止漏写子类时,异常被错误归类为"系统异常"。
-
细分异常需要创建子类(比如 UsernameDuplicateException),继承父类 BusinessException;
-
全局异常类里要手动写每个子类异常的处理方法 (return Result.fail (xxx));
-
父类兜底方法是为了 "漏写子类异常时兜底"。
- 自定义异常类(父类 + 子类)里只定义 "异常类型" ,不写处理逻辑!
- 异常的处理逻辑(return Result.fail (...))全部写在全局异常类(ErrorAdvice) 里,自定义异常类只负责 "标识异常类型"。

最简实践
如果觉得子类太多麻烦,可以先不用写子类,只做两步:
- 写 1 个父类异常 BusinessException;
- 全局异常类里只写父类兜底方法。
代码示例:

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")。