🏆本文收录于「滚雪球学SpringBoot」专栏,专业攻坚指数级提升持续更新中,up!up!up!!
📜 前言
哈喽,家人们,有没有被“空指针异常”搞得怀疑人生的经历?今天我就亲身体验了一把,这波真的是直接裂开!
事情是这样的:有位用户在使用 ERP 系统时,为了偷懒不填必填字段(后来排查才知道,真是服了),直接敲了一堆空格绕过校验,成功提交了数据。结果,这问题可就来了——领导点开详情页面,发现页面一片空白。于是,领导火速在 DD 群里艾特我,要求赶紧处理。说实话,看到那一刻我确实有点紧张:页面空白?不对劲啊,这种低级问题怎么可能出现在我负责的系统里?
一顿线上 Debug 排查后,终于锁定了罪魁祸首:接口报了空指针异常。仔细一看,不得了,竟然是某个必填字段校验居然通过了!再深挖一下代码才发现,问题出在 @NotNull
注解上。同事用这个校验非空,却没处理字符串全是空格的情况。翻查提交记录,嗯,这功能是我安排同事做的,还是个新人任务……
当场裂开 💔!虽说是个低级错误,但作为带新人的我来说,出这种问题着实有点打脸。不过,回头一想,这次事故也算给我提了个醒:带新人不仅要安排任务,还得盯紧代码质量,尤其是细节校验这种地方。成长的代价有点惨痛,特别是脸(pia的很响)
面对NullPointerException
异常,它就像幽灵一般,总是在你最不设防的时候突然冒出来。面对我同事,我只能耐心安慰她,并跟她剖析原理,毕竟当初面她时决策是否录取我也有占了很大比重。
所以说,今天我就特地出一期,给同学们特别是我同事,好好上一课,👀会的同学可以粗略过一遍,不会的同学特别是我那同事!认真看好好学,别走神,因为我只教这一遍。准备好了吗?我要开始咯。
📚 目录
- 🧐 什么是
@NotNull
?它的作用是什么? - 🎩
@NotNull
的基本用法 - ✍️ 实战演示:如何在 Spring Boot 项目中使用
@NotNull
- 🔄
@NotNull
与其他校验注解的区别(@NotEmpty、@NotBlank) - 🛠️ 注解搭配:组合实现更强大的校验
- 🤓 底层原理:@NotNull 是如何工作的?
- 💡 最佳实践与常见坑
- 📈 总结与心得
🧐 什么是 @NotNull
?它的作用是什么?
@NotNull
注解,它是 Java Bean Validation 提供的一个注解,@NotNull
用于验证对象是否不为 null
。通常用来标注一个字段、方法参数、方法返回值等,表示该值不能为 null。它是由 Java Bean Validation API 提供的,属于 javax.validation.constraints 包,与@NotBlank
一个出处。该注解的作用是对字段或参数进行约束,确保其值在运行时不为 null,否则会触发验证失败并抛出相应的错误信息。它适合用于任何类型的字段或方法参数,只要你希望这个值不能是 null
,就可以使用 @NotNull
来确保它有值。通俗一点说,这个注解就是在告诉程序:如果你敢为空,那咱们就没完!而上述这个车祸现场,确不适合用它!适合用@NotBlank
。
主要属性:
- message:自定义验证失败时的错误消息。可以使用占位符,例如
{javax.validation.constraints.NotNull.message}
,这会引用默认的消息模板,或者你可以自己设置如"Name cannot be null"
。
@NotNull(message = "Name cannot be null")
private String name;
- groups:用于指定验证分组。通过这个属性,可以定义不同的验证场景。例如,可以在某些情况下跳过某些验证,只验证特定的组。
@NotNull(groups = Create.class)
private String name;
- payload:用于传递额外的信息,通常用于与框架交互传递特定的元数据。在一般使用中不太常见,更多地是框架扩展时使用。
@NotNull 注解属于 javax.validation.constraints 包,可从源码中进行查看,一般使用都需要导入import javax.validation.constraints.*;
。
而且,它的应用场景也相对校广,比如数据库的主键、用户名、订单ID等,这些值必须有值,不能留空。@NotNull
可以让你不必在代码中写一堆“!= null
”的if else 判断,大大减少这种逻辑校验,且提升代码简洁性与低耦合。
🎩 @NotNull
的基本用法
@NotNull
的使用非常直观,只需将它放在希望不为 null
的字段或参数前即可。还可以通过设置 message
属性,自定义当验证失败时的提示信息。
示例代码
比如,我们有一个 User
类,要求用户名 name
是必填项:
import javax.validation.constraints.NotNull;
public class User {
@NotNull(message = "用户名不能为空")
private String name;
private Integer age;
// Getter 和 Setter
}
在这里,@NotNull
保证了 name
必须有值,不得为空。Spring 会在接收到数据时自动进行检查,如果 name
为空,就会抛出验证错误,并返回我们设置的提示信息“用户名不能为空”。但如果你得需求要保证 name
不为空串,那么你选择用它就不合适了。
不信,这里我给大家做个实验,大家请看:
很明显结果已定,如果是要用@NotNull
注解,那就判断不了空格值的场景,这点大家需要慎重鉴定,别跟我同事一样犯这种低级错误哦。
✍️ 实战演示:如何在项目中使用 @NotNull
接下来,我们来看看在 Spring Boot 项目中,@NotNull
是如何与控制器和异常处理器协作,进行全面的校验。
Step 1: 导入对应依赖
首先,为了使用 @NotNull
,你需要在项目中引入一个支持 Jakarta Bean Validation 的实现库,比如 Hibernate Validator。
以下是常见的 Maven 依赖配置:
Maven 配置:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.2.5.Final</version> <!-- 请根据需要选择版本 -->
</dependency>
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
Gradle 配置:
implementation 'org.hibernate.validator:hibernate-validator:6.2.5.Final'
implementation 'javax.validation:validation-api:2.0.1.Final'
这个根据大家具体的项目依赖构建方式而定,而选择对应的方式导入依赖。
Step 2: 创建验证实体类
假设我们有一个用户注册请求,其中 name
和 email
是必填的,而年龄 age
是可选的。我们可以使用 @NotNull
标记这些必填字段。
import javax.validation.constraints.NotNull;
public class RegistrationRequest {
@NotNull(message = "姓名不能为空")
private String name;
@NotNull(message = "邮箱不能为空")
private String email;
private Integer age;
// Getter 和 Setter
}
Step 3: 在 Controller 中使用 @Validated
触发验证
在控制器方法中,我们可以使用 @Valid
注解来触发验证逻辑,如果 name
或 email
字段为 null
,Spring 会自动返回错误提示。
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/system/testUser")
public class RegistrationController {
/**
* 新增user
*/
@ApiOperation("新增user")
@PostMapping("/add")
public R<Void> add(@Validated @RequestBody User user) {
return toR(userService.insert(user));
}
}
Step 4: 使用全局异常处理器捕获验证失败
为了更优雅地处理验证失败的情况,我们可以使用全局异常处理器,将错误信息集中管理。
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String handleValidationExceptions(MethodArgumentNotValidException ex) {
return ex.getBindingResult().getFieldError().getDefaultMessage();
}
}
这样一来,当验证失败时,系统会返回我们自定义的错误信息,更加直观友好。
Step 5: 实际演示
为了验证如上代码注解是否真正能够拦截这些情景,我们直接上Postman来模拟调用接口测试一下。
模拟场景1:用户名直接传null值
这里我们请求的时候,将name直接传null值进行请求。Postman请求如下:
如上成功验证若用户名传入null值则自动会被拦截,且提示对应的message信息,如“姓名不能为空”。
模拟场景2:邮箱号直接传null值
继续模拟,这里我们在发请求的时候,将email直接传null值。Postman请求如下:
如上成功验证邮箱号传null值也依然会被拦截,强行被提示“邮箱不能为空”。
总之,这里已经替大家都测试过了,大家可放心食用。
🔄 @NotNull
与其他校验注解的区别(@NotEmpty、@NotBlank)
Java Bean Validation 它提供了多个用于非空校验的注解,每个注解的适用范围和检查内容都不同。我们特地来看看 @NotNull
与 @NotEmpty
、@NotBlank
的区别。
@NotNull
:用于验证字段不为null
,允许空字符串或空集合。@NotEmpty
:不仅要求字段不为null
,还不允许空集合或空字符串。@NotBlank
:最严格的检查,不允许null
,且字符串内容不能全是空格。
public class Example {
@NotNull(message = "此字段不能为空")
private List<String> notNullList;
@NotEmpty(message = "此字段不能为空集合")
private List<String> notEmptyList;
@NotBlank(message = "此字段不能全为空白字符")
private String notBlankField;
}
@NotNull
是最基本的检查,它仅保证字段有值;如果需要更严格的校验,比如检查字符串或集合是否有内容,还需要结合其他注解,所以说,看到这里,可别再乱用这三个注解啦。
🛠️ 注解搭配:组合实现更强大的校验
在实际开发中,@NotNull
通常与其他校验注解配合使用,比如 @Size
、@Pattern
等,以满足更复杂的校验需求。
例如,我们希望用户名不仅不能为空,还必须是至少 5 个字符以上的字符串,这时可以组合 @NotNull
和 @Size
:
import javax.validation.constraints.Size;
public class UserRequest {
@NotNull(message = "用户名不能为空")
@Size(min = 5, message = "用户名至少包含5个字符")
private String name;
@NotNull(message = "邮箱不能为空")
private String email;
}
通过这种组合方式,我们可以实现更精准的数据控制。
🤓 底层原理:@NotNull 是如何工作的?
@NotNull
注解依赖于 Java Bean Validation API(JSR 380),通常由 Hibernate Validator 等实现库支持。当我们在字段上标记了 @NotNull
,Spring 会在数据传入时自动触发验证逻辑。如果字段的值为 null
,验证就会失败,因此请求也就直接会被拦截无法通过。
这意味着,我们只需在实体类上标记 @NotNull
,校验过程便会在数据传入时自动执行,无需额外手动代码。这种机制大大提升了代码的简洁性和数据校验的一致性。
接着我们重点来剖析下是它的工作原理:
1. 注解作用
@NotNull
注解的作用是标注某个字段、方法参数或方法返回值不能为 null
。如果该值为 null
,在验证时会触发验证失败。
2. 验证机制
依赖验证实现(如 Hibernate Validator)
@NotNull
依赖于 Java Bean Validation API 来执行约束检查。该 API 定义了约束的注解(如 @NotNull
),然后通过实现(如 Hibernate Validator)执行实际的验证工作。
通常,验证过程包括以下步骤:
- 创建 Validator 工厂:通常你会创建一个
ValidatorFactory
,然后通过它获取一个Validator
实例。 - 执行验证:通过调用
validate()
或validateProperty()
方法来验证对象或对象的某个属性。 - 获取验证结果:验证的结果会返回一组
ConstraintViolation
对象,描述违反约束的具体信息。
3. 验证过程
当验证器(如 Hibernate Validator)执行 @NotNull
的验证时,它会:
- 读取注解的属性:例如,
message
、groups
、payload
等。 - 检查目标值是否为
null
:如果值是null
,则会产生验证错误。 - 产生验证结果:如果验证失败,
ConstraintViolation
会保存错误信息,表示验证失败的字段或参数。
4. 使用场景
a. 验证字段
@NotNull
通常用于类的字段,确保该字段不能为空。以下是一个例子:
public class User {
@NotNull(message = "Username cannot be null")
private String username;
}
验证时,如果 username
为 null
,就会触发验证错误,返回指定的错误消息。
b. 验证方法参数
你也可以在方法的参数上使用 @NotNull
,确保传入的参数不为空。
public void setUsername(@NotNull String username) {
this.username = username;
}
如果调用 setUsername()
时传入 null
,会触发验证失败。
c. 验证方法返回值
@NotNull
也可以用于方法返回值,确保返回值不为 null
。
@NotNull(message = "Returned value cannot be null")
public String getUsername() {
return username;
}
5. 验证的执行
通常,@NotNull
验证是在对象的生命周期中的某个时刻触发的。常见的触发方式包括:
- 在数据持久化前(如 JPA/Hibernate):验证数据在存入数据库之前不为空。
- 在方法调用时:通过验证方法的参数,确保传入的参数不能为空。
- 在框架(如 Spring)中:通过使用
@Valid
或@Validated
注解来触发验证。
6. 错误处理
如果验证失败,验证器会抛出异常或返回验证错误信息:
- 验证失败时抛出异常:
@NotNull
的验证失败会触发ConstraintViolationException
异常。在一些框架(如 Hibernate Validator)中,验证失败会将错误信息保存在ConstraintViolation
对象中。
@NotNull(message = "Field cannot be null")
private String field;
Set<ConstraintViolation<User>> violations = validator.validate(user);
if (!violations.isEmpty()) {
// 处理验证失败的情况
}
- 验证失败时返回错误信息:例如,Spring 中使用
@Valid
进行参数验证,验证失败时返回错误消息。
💡 最佳实践与常见坑
-
正确选择注解:如果只需要基本的非空校验,
@NotNull
是首选;但如果需要检查集合或字符串的内容,使用@NotEmpty
或@NotBlank
更合适(严重强调!)。 -
明确错误信息:在
@NotNull
的message
属性中提供清晰的错误提示,帮助用户快速定位问题。 -
结合全局异常处理:将校验异常集中处理,可以提升代码的可读性和可维护性。
-
注意注解顺序:如果多个注解组合使用,确保
@NotNull
在最前面,保证非空检查优先执行。
📈 总结与心得
@NotNull
注解虽然简单,但在数据完整性校验中可是重中之重。它不仅可以省去大量“! = null” 的表单字段校验之外,减少由于数据不完整导致的错误,还能提高代码的可读性。通过合理地使用 @NotNull
,并结合其他注解,可实现更灵活、更细致的数据验证,谁用谁爽系列之一。
希望这篇文章能够帮助你(特别是同事你!如果你看到了这篇文章的话🤬)更好地理解 @NotNull
的使用场景和注意事项,还在用if else 校验的同学们赶紧在项目中试试吧!🎉
📣 关于我
我是bug菌,CSDN | 掘金 | InfoQ | 51CTO | 华为云 | 阿里云 | 腾讯云 等社区博客专家,C站博客之星Top30,华为云2023年度十佳博主,掘金多年度人气作者Top40,掘金等各大社区平台签约作者,51CTO年度博主Top12,掘金/InfoQ/51CTO等社区优质创作者;全网粉丝合计 30w+;硬核微信公众号「猿圈奇妙屋」,欢迎你的加入!免费白嫖最新BAT互联网公司面试真题、4000G PDF电子书籍、简历模板等海量资料,你想要的我都有,关键是你不来拿哇。
-End-