Spring Boot参数校验实战NotEmpty、NotBlank、NotNull的精准选择指南在Java开发中数据校验是保证系统健壮性的第一道防线。Spring Boot提供了丰富的校验注解但很多开发者在面对NotEmpty、NotBlank和NotNull时常常感到困惑——它们看起来相似实际效果却大不相同。本文将带你深入理解这三种注解的细微差别并通过真实业务场景演示如何避免常见的校验陷阱。1. 核心概念解析三种注解的本质区别1.1 NotNull最基础的非空校验NotNull是三种注解中限制最宽松的一个它只检查对象引用是否为null不考虑对象内部的内容或状态。这意味着public class User { NotNull private String username; // 允许或 但不允许null }适用场景基本数据类型包装类如Integer、Long任何需要确保非null引用的对象类型当后续处理可能直接调用对象方法时1.2 NotEmpty集合与字符串的非空检查NotEmpty在NotNull的基础上增加了长度检查适用于字符串、集合和数组public class OrderRequest { NotEmpty private ListOrderItem items; // 不能为null且size()0 NotEmpty private String address; // 不能为null且length()0 }关键特性字符串检查length() 0集合/数组检查size() 0不执行trim操作纯空格字符串 可以通过校验1.3 NotBlank字符串的严格校验NotBlank是专门为字符串设计的严格校验要求字符串在去除首尾空格后仍有实际内容public class LoginForm { NotBlank private String username; // 不允许null、或 }特殊行为自动调用trim()方法仅适用于CharSequence类型主要是String最严格的字符串校验方式对比表格三种注解的核心差异特性NotNullNotEmptyNotBlank允许null❌❌❌允许空字符串✅❌❌允许纯空格✅✅❌适用类型所有类型字符串/集合仅字符串trim处理无无自动执行2. 实战场景分析五种典型业务案例2.1 用户注册表单校验错误示范public class RegisterDTO { NotNull // 不够严格 private String username; }问题用户可能提交空字符串或纯空格作为用户名正确做法public class RegisterDTO { NotBlank(message 用户名不能为空) private String username; NotEmpty // 比NotBlank更合适 private String password; // 允许纯空格密码考虑加密后仍有效 }经验用户名这类需要展示的字段应使用NotBlank而密码等需要加密处理的字段可能更适合NotEmpty2.2 REST API的ID参数校验常见错误GetMapping(/users/{id}) public User getUser(PathVariable NotEmpty String id) { // 可能抛出MethodArgumentTypeMismatchException }问题NotEmpty会导致Spring尝试将空字符串转换为路径变量引发类型转换异常解决方案GetMapping(/users/{id}) public User getUser(PathVariable NotBlank String id) { // 更合理的校验 }最佳实践路径参数优先使用NotBlank查询参数根据业务选择NotNull或NotBlank2.3 集合类型参数校验典型场景public class BatchDeleteRequest { NotNull // 不够 private ListLong ids; }风险虽然防止了null但空列表仍可能被提交改进方案public class BatchDeleteRequest { NotEmpty(message ID列表不能为空) private ListLong ids; }扩展思考对于分页查询如果允许空结果集则应使用NotNull而非NotEmpty2.4 数字类型字段校验常见误区public class PaymentRequest { NotEmpty // 编译错误 private BigDecimal amount; }正确方式public class PaymentRequest { NotNull DecimalMin(0.01) private BigDecimal amount; }关键点数字类型只能用NotNull结合Min/Max/DecimalMin等实现范围控制2.5 多层级对象校验复杂对象示例public class OrderDTO { NotBlank private String orderNo; Valid // 关键注解 NotNull private UserDTO user; NotEmpty private ListValid OrderItemDTO items; }注意事项对象属性需要NotNull而非其他两种必须添加Valid触发嵌套校验集合内元素的校验同样需要Valid3. 高级技巧与常见陷阱3.1 自定义错误消息优化基础用法NotBlank(message {user.name.notblank}) private String username;高级技巧# messages.properties user.name.notblank用户名不能为空请填写实际内容最佳实践使用消息编码而非硬编码文本支持国际化消息模板中可包含参数3.2 分组校验策略场景同一对象在不同接口需要不同校验规则实现方案public class UserDTO { interface Create {} interface Update {} NotBlank(groups Create.class) private String password; NotBlank(groups {Create.class, Update.class}) private String username; }使用方式PostMapping(/users) public void createUser(Validated(UserDTO.Create.class) RequestBody UserDTO user) { // ... }3.3 校验顺序控制问题场景当多个校验注解同时存在时执行顺序可能影响性能优化方案public class ProductDTO { NotNull Size(max 100) Pattern(regexp ^[a-zA-Z0-9]$) private String productCode; }执行顺序原则先执行NotNull最轻量级再执行Size等基础校验最后执行Pattern等复杂校验3.4 组合注解的应用重复代码示例public class ArticleDTO { NotBlank Size(max 100) private String title; NotBlank Size(max 100) private String subtitle; }优化方案Documented Constraint(validatedBy {}) NotBlank Size(max 100) Retention(RetentionPolicy.RUNTIME) public interface NotBlankAndSize { String message() default ; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; }使用方式public class ArticleDTO { NotBlankAndSize private String title; }4. 性能优化与最佳实践4.1 校验注解的性能影响性能对比单字段百万次校验注解平均耗时(ms)NotNull120NotEmpty150NotBlank180优化建议简单场景优先使用NotNull避免在循环内部执行校验对已知安全的数据可跳过校验4.2 与Lombok的协同使用典型问题Data public class User { NotBlank private final String username; // 可能导致校验失效 }解决方案Data RequiredArgsConstructor public class User { NotBlank private final String username; }关键点确保构造函数能被校验框架识别考虑使用Builder时的校验策略4.3 测试策略建议单元测试示例Test void should_throw_exception_when_username_is_blank() { User user new User( ); SetConstraintViolationUser violations validator.validate(user); assertThat(violations) .extracting(message) .contains(用户名不能为空); }测试要点边界值测试null、、 、a类型兼容性测试嵌套对象测试在实际项目中我发现很多团队过度使用NotBlank而忽视NotEmpty的适用场景。比如密码字段使用NotBlank可能导致用户意外输入空格而无法察觉而日志系统记录的却是trim后的内容给问题排查带来困难。