🎉🎉《Spring Boot实战案例合集》目前已更新168个案例,我们将持续不断的更新。文末有电子书目录。
💪💪永久更新承诺
我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务。
💌💌如何获取
订阅我们的合集《点我订阅》,并通过私信联系我们,我们将第一时间将电子书发送给您。
环境:SpringBoot3.4.2
1. 简介
在开发中,确保传入方法的参数合法有效是保障系统稳定运行的关键。传统参数验证方式代码冗余且难以维护。而基于注解的参数验证应运而生,它借助如Spring Validation等框架,通过在参数或参数所属类属性上添加简单注解,如@NotNull、@Size等,就能在方法调用前自动完成验证逻辑,极大简化代码,提升开发效率与可维护性。
本篇文章将详细的介绍基于注解参数验证的9大技巧。
2.1 基本注解
Spring Validation 提供了一组用于常见验证任务的标准、开箱即用的注解。如下示例:
public class UserDTO {private Long id;private String username;private String email;private Integer age;private LocalDate birthDate;private String phone;}
接下来,我们就可以在Controller接口参数上开启验证功能:
("/api/users")public class UserController {public ResponseEntity<UserDTO> createUser(UserDTO userDTO,BindingResult bindingResult) {if (bindingResult.hasErrors()) {throw new ValidationException(bindingResult);}return ResponseEntity.ok(userDTO);}}
注意:这里你使用 @Valid 和 @Validated 或者任何以 "Valid"开头的注解都可以。
2.2 自定义注解验证
对于特定的业务规则,你可以创建自己的自定义验证约束。
public UniqueUsername {String message() default "用户名已存在";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
自定义验证逻辑
public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsername, String> {private UserRepository userRepository;public boolean isValid(String username, ConstraintValidatorContext context) {if (username == null) {return true; // Let @NotNull handle null values}return !userRepository.existsByUsername(username);}}
2.3 分组验证
分组验证允许你针对不同场景应用不同的规则,例如创建操作与更新操作。如下示例(定义分组):
public interface ValidationGroups {interface Create {}interface Update {}}
使用分组
public class ProductDTO {@Null(groups = ValidationGroups.Create.class, message = "创建商品时ID必须为空")@NotNull(groups = ValidationGroups.Update.class, message = "更新商品时商品ID不能为空")private Long id;@NotBlank(groups = {ValidationGroups.Create.class, ValidationGroups.Update.class})private String name;}
分组验证
public ResponseEntity<ProductDTO> createProduct((ValidationGroups.Create.class) ProductDTO productDTO) {return ResponseEntity.ok(productDTO);}("/{id}")public ResponseEntity<ProductDTO> updateProduct( Long id,(ValidationGroups.Update.class) ProductDTO productDTO) {return ResponseEntity.ok(productDTO);}
注意:使用 @Validated 注解,因为它是支持验证组的注解。@Valid 不支持。
2.4 嵌套验证
对于复杂对象,你可以验证嵌套对象和集合。
public class OrderDTO {// 嵌套验证private CustomerDTO customer;// 嵌套验证private List<OrderItemDTO> items;}public class CustomerDTO {private String name;private String email;// 嵌套验证private AddressDTO address;}
注意:使用 @Valid 注解需要级联验证的字段,以确保验证器深入检查嵌套对象。
2.5 方法级验证
你也可以直接对服务层方法进行验证,而不仅仅是对控制器参数进行验证。
public class UserService {public User createUser( UserDTO userDTO) {return new User();}public User findById((1) Long id) {return new User();}}
注意,如果你是在Controller方法上那么你 不需要 在类上使用@Validated注解。
2.6 错误消息国际化
当发生错误时我们可以通过2中方式来提示具体的错误消息。
直接指定错误消息
(message = "姓名不能为空")private String name ;(message = "住址不能为空")private String address ;
直接通过 message 指定要展示的错误消息。
使用占位符
message属性可以使用 {xxx} 语法,其中 xxx 为你在资源文件中定义的key。
(message = "{name.empty.error}")private String name ;(message = "{address.empty.error}")private String address ;
接下来,新建 classpath:messages_zh_CN.properties 和 classpath:messages_en_US.properties 资源文件。
#messages_zh_CN.propertiesname.empty.error=\u59D3\u540D\u5FC5\u987B\u586B\u5199address.empty.error=\u4F4F\u5740\u5FC5\u987B\u586B\u5199#messages_en_US.propertiesname.empty.error=nameemptyerroraddress.empty.error=addressemptyerror
以上就完成了国际化错误消息的展示,上面我们是使用的spring boot默认的basename,你可以自定义,通过 spring.messages.basename 属性配置。
2.7 编程验证
除了注解之外,你还可以手动触发验证。
public class ValidationService {private final Validator validator;public ValidationService(Validator validator) {this.validator = validator;}public <T> void validate(T object) {Set<ConstraintViolation<T>> violations = validator.validate(object);if (!violations.isEmpty()) {throw new ConstraintViolationException(violations);}}}
适用于验证是有条件的复杂业务逻辑,或用于验证未通过控制器传递的对象。
2.8 组合验证
将多个基本约束组合成一个单一的、可复用的且更具表现力的注解。
message = "密码必须包含至少一个数字、小写字母、大写字母和特殊字符")public StrongPassword {String message() default "密码不符合安全要求";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
使用组合注解
public class PasswordChangeDTO {private String newPassword;}
优点:提高代码可读性,并确保验证规则得到一致应用。
2.9 跨字段验证
根据字段与其他字段的关系来验证字段,例如确保 “确认密码” 字段与 “密码” 字段匹配。创建一个类级约束:
public PasswordMatches {String message() default "密码不匹配";String field();String fieldMatch();Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}
密码验证器
public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatches, Object> {private String field;private String fieldMatch;public void initialize(PasswordMatches constraintAnnotation) {this.field = constraintAnnotation.field();this.fieldMatch = constraintAnnotation.fieldMatch();}public boolean isValid(Object value, ConstraintValidatorContext context) {try {// 通过反射获取字段值Field field1 = value.getClass().getDeclaredField(field);field1.setAccessible(true);Object fieldValue1 = field1.get(value);Field field2 = value.getClass().getDeclaredField(fieldMatch);field2.setAccessible(true);Object fieldValue2 = field2.get(value);// 比较两个字段值(处理null情况)if (fieldValue1 == null) {return fieldValue2 == null;}return fieldValue1.equals(fieldValue2);} catch (NoSuchFieldException | IllegalAccessException e) {throw new RuntimeException("Failed to validate password fields", e);}}}
使用注解
field = "password",fieldMatch = "confirmPassword",message = "两次密码不匹配")public class UserRegistrationDTO {private String password;private String confirmPassword;}
Controller接口
("/register")public ResponseEntity<?> register( UserRegistrationDTO dto, BindingResult result) {if (result.hasErrors()) {List<String> errors = result.getAllErrors().stream().map(err -> err.getDefaultMessage()).toList() ;return ResponseEntity.badRequest().body(errors);}return ResponseEntity.ok("Registration successful");}
关于这种跨字段的验证,请查看下面这篇文章:
优雅!Spring Boot 只需一个注解,搞定任意参数验证
推荐文章
性能优化!Spring Boot 弃用 Jackson,性能提升50%
拒绝重复造轮子!Spring Boot 六大核心AOP切面,开发必备
别再错过!Spring Boot 12个高级开发技巧,助你成为大神
Spring Boot 记录Controller接口请求日志7种方式,第六种性能极高
Spring Boot 通过@JsonComponent注解完全控制JSON数据
性能狂飙!Spring Boot 基于注解的 8 个缓存应用技巧
实体 到 DTO转换!这7种方式性能差距太大,最后一个才是王者


