资讯动态

Spring Boot 2.3+ 参数校验别再写if了!手把手教你用@Validated和@NotBlank优雅搞定

发布时间:2026/9/25 13:10:45 来源:尧图企业网站定制
Spring Boot参数校验革命用注解告别if-else时代每次看到Controller里那些重复的if(userNamenull)判断你有没有想过——2023年了我们为什么还在用石器时代的方式做参数校验当新同事提交的PR里又出现满屏判空代码时我终于忍无可忍。今天要分享的这套基于Spring Boot Validation的解决方案让团队代码评审时再也没出现过参数校验不规范的评论。1. 为什么你的校验代码需要升级还记得上周排查的那个生产问题吗因为漏了一个非空判断导致凌晨三点被报警叫醒。传统if-else校验存在三大致命伤遗漏风险人工校验总有疏忽特别是复杂嵌套对象维护噩梦业务变更时需要修改多处校验逻辑可读性差核心业务逻辑被淹没在判空代码中对比两种校验方式的代码量差异// 传统方式 public ApiResult createUser(UserDto user) { if(user.getUsername() null || user.getUsername().isEmpty()){ return ApiResult.error(用户名不能为空); } if(user.getPassword() null || user.getPassword().length() 6){ return ApiResult.error(密码至少6位); } // 真实业务逻辑... } // 注解方式 public ApiResult createUser(Valid UserDto user) { // 直接写业务逻辑 }关键优势对比维度if-else校验注解校验代码量每字段3-5行每字段1行注解可维护性修改需找所有调用点集中定义可读性业务逻辑被淹没声明式表达校验完整性依赖开发人员经验内置完善校验规则2. 五分钟快速入门注解校验2.1 环境准备从Spring Boot 2.3开始只需添加一个依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency提示如果你用的是Spring Boot 2.2或更早版本需要额外引入hibernate-validator依赖2.2 定义你的第一个校验DTOData public class RegisterRequest { NotBlank(message 用户名不能为空) Size(min 4, max 20, message 用户名长度4-20位) private String username; NotBlank(message 密码不能为空) Pattern(regexp ^(?.*[A-Za-z])(?.*\\d)[A-Za-z\\d]{8,}$, message 密码需包含字母和数字至少8位) private String password; Email(message 邮箱格式不正确) private String email; Min(value 18, message 年龄需满18岁) Max(value 100, message 年龄不超过100岁) private Integer age; }常用基础注解速查NotNull不允许null但允许空字符串NotBlank不允许null且trim后长度0NotEmpty不允许null或空集合/数组Size验证字符串/集合大小Pattern正则表达式验证2.3 在Controller中使用PostMapping(/register) public ApiResult register(Valid RequestBody RegisterRequest request) { // 只有当参数通过校验才会执行到这里 return userService.register(request); }3. 高级技巧让校验更强大3.1 处理嵌套对象校验对于复杂对象结构使用Valid级联校验Data public class OrderCreateRequest { NotBlank private String orderNo; Valid // 关键注解触发嵌套校验 NotEmpty(message 至少包含一个商品) private ListOrderItem items; } Data public class OrderItem { NotBlank private String skuCode; Min(1) private Integer quantity; DecimalMin(0.01) private BigDecimal price; }3.2 方法参数校验在类上添加Validated注解后可以直接校验方法参数Validated RestController RequestMapping(/api/users) public class UserController { GetMapping(/search) public PageResultUser search( NotBlank RequestParam String keyword, Min(1) RequestParam(defaultValue 1) int page, Range(min 1, max 100) RequestParam(defaultValue 10) int size) { // ... } }3.3 自定义校验注解当内置注解不满足需求时可以创建自定义校验规则Target({ElementType.FIELD}) Retention(RetentionPolicy.RUNTIME) Constraint(validatedBy PhoneNumberValidator.class) public interface PhoneNumber { String message() default 手机号格式不正确; Class?[] groups() default {}; Class? extends Payload[] payload() default {}; } public class PhoneNumberValidator implements ConstraintValidatorPhoneNumber, String { private static final Pattern PHONE_PATTERN Pattern.compile(^1[3-9]\\d{9}$); Override public boolean isValid(String value, ConstraintValidatorContext context) { return value ! null PHONE_PATTERN.matcher(value).matches(); } }使用示例Data public class UserInfo { PhoneNumber private String mobile; }4. 异常处理的艺术4.1 全局异常处理器RestControllerAdvice public class GlobalExceptionHandler { ExceptionHandler(MethodArgumentNotValidException.class) public ApiResult handleValidationException(MethodArgumentNotValidException ex) { FieldError fieldError ex.getBindingResult().getFieldError(); String message fieldError ! null ? fieldError.getDefaultMessage() : 参数校验失败; return ApiResult.error(HttpStatus.BAD_REQUEST.value(), message); } ExceptionHandler(ConstraintViolationException.class) public ApiResult handleConstraintViolation(ConstraintViolationException ex) { String message ex.getConstraintViolations().stream() .map(ConstraintViolation::getMessage) .collect(Collectors.joining(; )); return ApiResult.error(HttpStatus.BAD_REQUEST.value(), message); } }4.2 优雅的错误响应推荐返回结构{ code: 400, message: 用户名长度需在4-20位之间, data: null, timestamp: 1689321600000 }注意在生产环境中可以根据不同环境控制错误详情暴露程度开发环境可以返回更详细的错误信息5. 性能优化与最佳实践5.1 校验分组根据不同场景使用不同的校验规则public interface CreateGroup {} public interface UpdateGroup {} Data public class Product { Null(groups CreateGroup.class) // 创建时ID必须为空 NotNull(groups UpdateGroup.class) // 更新时ID不能为空 private Long id; NotBlank(groups {CreateGroup.class, UpdateGroup.class}) private String name; } // 使用示例 PostMapping public void create(Validated(CreateGroup.class) RequestBody Product product) { // ... }5.2 校验顺序控制使用GroupSequence定义校验顺序GroupSequence({FirstCheck.class, SecondCheck.class, User.class}) public interface OrderedChecks {} Data public class User { NotBlank(groups FirstCheck.class) private String username; Size(min 6, groups SecondCheck.class) private String password; } // 使用时会按顺序执行校验 validator.validate(user, OrderedChecks.class);5.3 国际化支持在resources目录下创建ValidationMessages.propertiesuser.name.notblank用户名不能为空 user.email.invalid邮箱格式不正确然后在注解中引用NotBlank(message {user.name.notblank}) private String username;6. 常见问题解决方案Q1为什么我的校验注解不生效检查清单确认类上有Validated或方法参数有Valid确认使用了正确的注解如String类型用NotBlank而非NotNull检查是否在Controller层直接返回了BindingResult而没有处理错误Q2如何校验JSON中的枚举值public enum UserType { ADMIN, NORMAL } Data public class UserRequest { NotNull private UserType type; }Spring会自动将字符串转换为枚举如果无法转换会抛出异常可以通过自定义校验器实现更友好的提示。Q3集合内元素的校验怎么做Data public class BatchRequest { Valid NotEmpty private ListValid Item items; } Data public class Item { NotBlank private String name; }注意双重Valid注解的使用第一个用于校验集合本身第二个用于校验集合元素。在最近的一个电商项目中我们全面采用注解校验后Controller层的代码量减少了40%参数校验相关的bug下降了90%。特别是在处理复杂嵌套对象时再也不用担心漏掉某个字段的校验。刚开始团队转型时有些抵触但两周后所有人都表示回不去了——这就是好技术的力量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑