文章目录
-
-
- 引言
- [一、先来认识一下 Spring Assert](#一、先来认识一下 Spring Assert)
-
- [1.1 它是什么?](#1.1 它是什么?)
- [1.2 为什么要用 Assert?](#1.2 为什么要用 Assert?)
- 二、核心方法全解析
-
- [2.1 非空判断:notNull / notEmpty / hasText](#2.1 非空判断:notNull / notEmpty / hasText)
- [2.2 布尔条件:isTrue](#2.2 布尔条件:isTrue)
- [2.3 状态检查:state](#2.3 状态检查:state)
- [2.4 类型检查:isInstanceOf / isAssignable](#2.4 类型检查:isInstanceOf / isAssignable)
- [2.5 noNullElements:集合/数组中不能包含 null 元素](#2.5 noNullElements:集合/数组中不能包含 null 元素)
- [2.6 其他](#2.6 其他)
- 三、源码深度剖析
-
- [3.1 notNull 的实现](#3.1 notNull 的实现)
- [3.2 hasText 的实现(稍有复杂度)](#3.2 hasText 的实现(稍有复杂度))
- [3.3 isInstanceOf 的实现](#3.3 isInstanceOf 的实现)
- [四、与 Java 原生断言的区别和联系](#四、与 Java 原生断言的区别和联系)
- [五、最佳实践:如何用好 Assert](#五、最佳实践:如何用好 Assert)
-
- [5.1 在公共方法入口处进行防御性校验](#5.1 在公共方法入口处进行防御性校验)
- [5.2 结合错误码,统一异常处理](#5.2 结合错误码,统一异常处理)
- [5.3 避免过度使用](#5.3 避免过度使用)
- [5.4 消息模板使用占位符](#5.4 消息模板使用占位符)
- 六、总结与展望
-
引言
当你还在手写
if (param == null) throw new IllegalArgumentException(...)时,Spring 已经帮你把这一切封装成了优雅的一行断言。
一、先来认识一下 Spring Assert
1.1 它是什么?
Assert 是 Spring Framework 中位于 org.springframework.util 包下的一个断言工具类,从 Spring 1.1.x 版本就已存在。它提供了一组静态方法,用于在代码中进行条件检查,当条件不满足时抛出指定的运行时异常(通常是 IllegalArgumentException 或 IllegalStateException)。
不同于单元测试中的断言(JUnit 的 Assert 类),Spring Assert 的目标是在业务代码和框架代码中进行防御性参数校验 ,让你用一行方法调用来替代传统的 if-throw 代码块,显著提升代码可读性和整洁度。
1.2 为什么要用 Assert?
在日常开发中,方法入口处的参数校验必不可少:
java
// 传统写法,又臭又长
public void register(String username, String password, Integer age) {
if (username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
if (password == null || password.length() < 6) {
throw new IllegalArgumentException("密码长度不能少于6位");
}
if (age == null || age < 0 || age > 150) {
throw new IllegalArgumentException("年龄必须在0-150之间");
}
// 业务逻辑...
}
这段代码中,业务逻辑被大量的参数校验淹没,不仅难以阅读,还容易遗漏或出错。
使用 Spring Assert 重构后:
java
// Spring Assert 方式
public void register(String username, String password, Integer age) {
Assert.hasText(username, "用户名不能为空");
Assert.isTrue(password != null && password.length() >= 6, "密码长度不能少于6位");
Assert.isTrue(age != null && age >= 0 && age <= 150, "年龄必须在0-150之间");
// 业务逻辑...
}
代码量减少 60%,意图一目了然。每个断言方法都包含了条件检查和异常消息,无需手写 if 和 throw。
二、核心方法全解析
Spring Assert 提供了 10+ 个静态方法,按功能可分为几大类。下面逐个解析,并给出典型使用场景。
2.1 非空判断:notNull / notEmpty / hasText
这是日常开发中使用频率最高的方法组。
| 方法 | 检查条件 | 抛出异常 | 适用场景 |
|---|---|---|---|
notNull(Object object, String message) |
object != null |
IllegalArgumentException |
对象不能为空 |
notEmpty(Collection<?> collection, String message) |
collection != null && !collection.isEmpty() |
IllegalArgumentException |
集合不能为空 |
notEmpty(Map<?,?> map, String message) |
map != null && !map.isEmpty() |
IllegalArgumentException |
Map不能为空 |
notEmpty(Object[] array, String message) |
array != null && array.length > 0 |
IllegalArgumentException |
数组不能为空 |
hasLength(String text, String message) |
text != null && text.length() > 0 |
IllegalArgumentException |
字符串有长度(允许空格) |
hasText(String text, String message) |
text != null && text.trim().length() > 0 |
IllegalArgumentException |
字符串有实际内容(非空格) |
注意 hasLength 与 hasText 的区别:
hasLength(" ")→ 通过(因为长度为2)hasText(" ")→ 失败(因为 trim 后为空)
实战示例:
java
public void processUser(User user) {
Assert.notNull(user, "用户对象不能为null");
Assert.hasText(user.getName(), "用户名不能为空或全空格");
Assert.notEmpty(user.getRoles(), "用户至少需要一个角色");
}
2.2 布尔条件:isTrue
当条件表达式较为复杂,无法用上述专有方法覆盖时,使用 isTrue。
java
Assert.isTrue(age >= 18 && age <= 60, "年龄必须在18-60之间");
Assert.isTrue(list.size() <= 100, "列表元素不能超过100个");
isTrue 是最通用的断言,内部只是一句 if (!expression) throw new IllegalArgumentException(message);。
2.3 状态检查:state
与 isTrue 功能相似,但抛出的是 IllegalStateException(非法状态异常),而非 IllegalArgumentException(非法参数异常)。语义上更适用于对象状态不符合预期的情况。
java
// 连接池关闭后无法获取连接
if (!pool.isOpen()) {
throw new IllegalStateException("连接池已关闭");
}
// 使用 Assert
Assert.state(pool.isOpen(), "连接池已关闭");
2.4 类型检查:isInstanceOf / isAssignable
用于检查类型关系,抛出 IllegalArgumentException。
java
// 确保对象是指定类型的实例
Assert.isInstanceOf(SubClass.class, obj, "obj必须是SubClass的实例");
// 确保某个类型可以赋值给另一个类型(继承或实现关系)
Assert.isAssignable(ParentClass.class, SubClass.class, "SubClass必须继承自ParentClass");
这些方法在框架代码或反射操作中非常有用。
2.5 noNullElements:集合/数组中不能包含 null 元素
java
List<String> list = Arrays.asList("a", null, "c");
Assert.noNullElements(list, "列表中不能包含null元素");
// 抛出 IllegalArgumentException: 列表中不能包含null元素
该方法会遍历集合,发现第一个 null 元素立即抛出异常。
2.6 其他
isNull:断言一个对象是{@code null}。doesNotContain:断言给定文本不包含给定子串。
PS:以上所有的断言方法在Spring 5.0+ 新增了 Supplier<String> 重载方法。消息参数均支持 接收一个 Supplier<String> 函数式接口参数 来构建断言提示消息;String 版本无论断言结果如何都会立即构建消息,而 Supplier 版本仅在断言失败时才延迟生成消息,从而避免正常路径下的性能开销。
三、源码深度剖析
3.1 notNull 的实现


非常简洁,没有多余的逻辑。这种简单性正是工具类的精髓------做且仅做一件事,做好它。
3.2 hasText 的实现(稍有复杂度)


内部调用了 StringUtils.hasText(String str),其实现为:

3.3 isInstanceOf 的实现







先检查目标类型不为空,再使用 Class.isInstance() 进行判断。注意这里没有对 obj 判空------因为 null 不是任何类的实例(除了 null 本身),所以 type.isInstance(null) 返回 false,会抛出异常,符合预期。
四、与 Java 原生断言的区别和联系
许多 Java 开发者知道 assert 关键字,但 Spring Assert 与它有着本质区别。
| 特性 | Spring Assert | Java 原生 assert |
|---|---|---|
| 启用方式 | 始终生效,无需 JVM 参数 | 需要 -ea 参数显式开启,生产环境通常关闭 |
| 抛出异常 | IllegalArgumentException, IllegalStateException |
AssertionError(Error 而非 Exception) |
| 语义 | 参数校验、状态校验(防御性编程) | 调试期间的不变量检查 |
| 能否在生产代码中使用 | 强烈推荐 | 不推荐(默认关闭,不会执行) |
| 性能影响 | 每次都会执行,但开销极小 | 关闭时零开销 |
结论 :Java 原生 assert 主要用作开发和测试阶段的内部不变量检查,不应依赖它进行生产环境的参数校验。而 Spring Assert 正是为了解决生产环境下的参数校验需求而设计的,是线上代码的第一道防线。
五、最佳实践:如何用好 Assert
5.1 在公共方法入口处进行防御性校验
java
public void updateUser(Long userId, UserDto dto) {
Assert.notNull(userId, "用户ID不能为空");
Assert.notNull(dto, "更新数据不能为空");
Assert.hasText(dto.getName(), "用户姓名不能为空");
// 业务逻辑...
}
5.2 结合错误码,统一异常处理
在实际项目中,通常不会直接把 IllegalArgumentException 抛给前端,而是配合全局异常处理器,将异常转换为统一的业务错误响应。
java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ErrorResponse> handleIllegalArg(IllegalArgumentException ex) {
return ResponseEntity.badRequest().body(new ErrorResponse(400, ex.getMessage()));
}
}
这样,Assert 抛出的异常就能被优雅地转换成前端可以理解的错误信息。
5.3 避免过度使用
Assert 并非万能。对于复杂校验(如跨字段校验、调用数据库校验),仍然需要手写逻辑。但 isTrue 可以作为这些复杂校验结果的最终断言。
java
// 复杂校验
boolean isValid = validateComplexCondition(order);
Assert.isTrue(isValid, "订单状态不满足取消条件");
5.4 消息模板使用占位符
Spring Assert 的消息参数仅支持字符串拼接,不支持 SLF4J 风格的占位符。如果你需要更灵活的消息格式化,可以自行拼接或使用 String.format。
java
Assert.notNull(user, String.format("用户[%s]不存在", userId));
六、总结与展望
Spring Assert 是一个看似简单却极其实用的工具类。它没有复杂的功能,只专注于一件事:用一行代码完成参数校验并抛出异常。这种"小而美"的设计,恰恰是 Spring 工具类的核心理念------为开发者的日常编程减负,让代码更清晰、更安全。
核心要点回顾:
- 用途:替代手写
if (condition) throw new XxxException,用于防御性参数校验。 - 时机:公共方法入口处、框架代码中、任何需要对输入或状态进行校验的地方。
- 优势:代码简洁、语义清晰、异常类型统一。
- 注意:不要与 Java 原生
assert混淆;Assert 永远生效,原生 assert 默认关闭。
下一篇文章我们将继续探索 "Spring 常用类深度剖析(工具篇 06):StopWatch:精准计时的优雅实现"。敬请期待!
思考题 :在你的项目中,除了参数校验,还有哪些场景可以用 Assert 来简化代码?尝试重构一段现有的 if-throw 代码,并对比重构前后的代码量和可读性。(欢迎在评论区分享你的重构案例)
答 :Spring
Assert的核心价值在于 "快速失败"(Fail-Fast)与"代码简洁" ,除了参数校验还有一些其他场景,其应用场景可总结为以下四点:
- 参数校验(前置条件)
确保方法入参合法(非空、非空集合、数值范围等),明确方法契约。
常用方法 :notNull,hasText,isTrue,notEmpty.- 状态检查(不变量维护)
确保对象在执行操作前处于正确的内部状态(如:未初始化不可使用、状态机流转合法)。
常用方法 :state(抛出IllegalStateException).- 类型安全(防御性编程)
在进行向下转型或反射调用前,验证对象类型兼容性,避免晦涩的ClassCastException。
常用方法 :isInstanceOf,isAssignable.- 结果验证(后置条件)
在关键逻辑执行后,断言结果符合预期(如:数据库保存后必须生成 ID),便于早期发现逻辑 Bug。