大数跨境
0
0

9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!

9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半! Spring全家桶实战案例
2025-09-09
2
导读:9个Spring Boot参数验证高阶技巧,第8,9个代码量直接减半!
Spring Boot 3实战案例锦集PDF电子书已更新至130篇!
图片

🎉🎉《Spring Boot实战案例合集》目前已更新168个案例,我们将持续不断的更新。文末有电子书目录。

💪💪永久更新承诺

我们郑重承诺,所有订阅合集的粉丝都将享受永久免费的后续更新服务

💌💌如何获取
订阅我们的合集点我订阅,并通过私信联系我们,我们将第一时间将电子书发送给您。

→ 现在就订阅合集

环境:SpringBoot3.4.2



1. 简介

在开发中,确保传入方法的参数合法有效是保障系统稳定运行的关键。传统参数验证方式代码冗余且难以维护。而基于注解的参数验证应运而生,它借助如Spring Validation等框架,通过在参数或参数所属类属性上添加简单注解,如@NotNull、@Size等,就能在方法调用前自动完成验证逻辑,极大简化代码,提升开发效率与可维护性。

本篇文章将详细的介绍基于注解参数验证的9大技巧。

2.实战案例

2.1 基本注解

Spring Validation 提供了一组用于常见验证任务的标准、开箱即用的注解。如下示例:

public class UserDTO {  private Long id;  @NotBlank(message = "用户名必须填写")  @Size(min = 4, max = 20, message = "用户名必须是4到20个字符")  private String username;  @Email(message = "无效的邮箱")  private String email;  @Min(value = 18, message = "年龄必须大于18")  @Max(value = 120, message = "年龄必须小于120")  private Integer age;  @Past(message = "错误的出生日期")  private LocalDate birthDate;  @Pattern(regexp = "^1[3-9]\\d{9}$", message = "电话号码错误")  private String phone;}

接下来,我们就可以在Controller接口参数上开启验证功能:

@RestController@RequestMapping("/api/users")public class UserController {  @PostMapping  public ResponseEntity<UserDTOcreateUser(    @RequestBody @Valid UserDTO userDTO,     BindingResult bindingResult) {    if (bindingResult.hasErrors()) {      throw new ValidationException(bindingResult);    }    return ResponseEntity.ok(userDTO);  }}

注意:这里你使用 @Valid 和 @Validated 或者任何以 "Valid"开头的注解都可以。

2.2 自定义注解验证

对于特定的业务规则,你可以创建自己的自定义验证约束。

@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = UniqueUsernameValidator.class)public @interface UniqueUsername {
  String message() default "用户名已存在";  Class<?>[] groups() default {};  Class<? extends Payload>[] payload() default {};}

自定义验证逻辑

public class UniqueUsernameValidator implements ConstraintValidator<UniqueUsernameString> {  @Resource  private UserRepository userRepository;  @Override  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.classValidationGroups.Update.class})  private String name;}

分组验证

@PostMappingpublic ResponseEntity<ProductDTOcreateProduct(  @RequestBody @Validated(ValidationGroups.Create.class) ProductDTO productDTO) {  return ResponseEntity.ok(productDTO);}@PutMapping("/{id}")public ResponseEntity<ProductDTOupdateProduct(@PathVariable Long id,   @RequestBody @Validated(ValidationGroups.Update.class) ProductDTO productDTO) {  return ResponseEntity.ok(productDTO);}

注意:使用 @Validated 注解,因为它是支持验证组的注解。@Valid 不支持。

2.4 嵌套验证

对于复杂对象,你可以验证嵌套对象和集合。

public class OrderDTO {  @NotNull  @Valid // 嵌套验证  private CustomerDTO customer;  @NotEmpty  @Valid // 嵌套验证  private List<OrderItemDTO> items;}public class CustomerDTO {  @NotBlank  private String name;  @Email  private String email;  @Valid // 嵌套验证  private AddressDTO address;}

注意:使用 @Valid 注解需要级联验证的字段,以确保验证器深入检查嵌套对象。

2.5 方法级验证

你也可以直接对服务层方法进行验证,而不仅仅是对控制器参数进行验证。

@Service@Validatedpublic class UserService {  public User createUser(@Valid UserDTO userDTO) {    return new User();  }  @NotNull  public User findById(@Min(1) Long id) {    return new User();  }}

注意,如果你是在Controller方法上那么你 不需要 在类上使用@Validated注解。

2.6 错误消息国际化

当发生错误时我们可以通过2中方式来提示具体的错误消息。

  • 直接指定错误消息

@NotEmpty(message = "姓名不能为空")private String name ;@NotEmpty(message = "住址不能为空")private String address ;

直接通过 message 指定要展示的错误消息。

图片
  • 使用占位符

message属性可以使用 {xxx} 语法,其中 xxx 为你在资源文件中定义的key。

@NotEmpty(message = "{name.empty.error}")private String name ;@NotEmpty(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 编程验证

除了注解之外,你还可以手动触发验证。

@Servicepublic class ValidationService {  private final Validator validator;  public ValidationService(Validator validator) {    this.validator = validator;  }  public <T> void validate(object) {    Set<ConstraintViolation<T>> violations = validator.validate(object);    if (!violations.isEmpty()) {      throw new ConstraintViolationException(violations);    }  }}

适用于验证是有条件的复杂业务逻辑,或用于验证未通过控制器传递的对象。

2.8 组合验证

将多个基本约束组合成一个单一的、可复用的且更具表现力的注解。

@NotNull@Size(min = 8, max = 30)@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=]).*$",         message = "密码必须包含至少一个数字、小写字母、大写字母和特殊字符")@Target({ElementType.FIELD, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = {})public @interface StrongPassword {
  String message() default "密码不符合安全要求";  Class<?>[] groups() default {};  Class<? extends Payload>[] payload() default {};}

使用组合注解

public class PasswordChangeDTO {
  @StrongPassword  private String newPassword;}

优点:提高代码可读性,并确保验证规则得到一致应用。

2.9 跨字段验证

根据字段与其他字段的关系来验证字段,例如确保 “确认密码” 字段与 “密码” 字段匹配。创建一个类级约束:

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Constraint(validatedBy = PasswordMatchesValidator.class)public @interface PasswordMatches {    String message() default "密码不匹配";  String field();  String fieldMatch();  Class<?>[] groups() default {};  Class<? extends Payload>[] payload() default {};}

密码验证器

public class PasswordMatchesValidator implements ConstraintValidator<PasswordMatchesObject> {  private String field;  private String fieldMatch;  @Override  public void initialize(PasswordMatches constraintAnnotation) {    this.field = constraintAnnotation.field();    this.fieldMatch = constraintAnnotation.fieldMatch();  }  @Override  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);    }  }}

使用注解

@PasswordMatches(    field = "password",    fieldMatch = "confirmPassword",    message = "两次密码不匹配")public class UserRegistrationDTO {  @NotBlank(message = "密码不能为空")  @Size(min = 8, message = "密码长度必须大于等于8个字符")  private String password;  @NotBlank(message = "确认密码不能为空")  private String confirmPassword;}

Controller接口

@PostMapping("/register")public ResponseEntity<?> register(@Valid @RequestBody 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 文件下载的N种神级姿势

生产环境Spring Boot切记要关闭这个开关

性能优化!Spring Boot 弃用 Jackson,性能提升50%

拒绝重复造轮子!Spring Boot 六大核心AOP切面,开发必备

别再错过!Spring Boot 12个高级开发技巧,助你成为大神

Spring Boot中JSON日期处理的5种骚操作

Spring Boot 记录Controller接口请求日志7种方式,第六种性能极高

Spring Boot 通过@JsonComponent注解完全控制JSON数据

强大!Spring Boot 一个注解搞定接口限流

性能狂飙!Spring Boot 基于注解的 8 个缓存应用技巧

实体 到 DTO转换!这7种方式性能差距太大,最后一个才是王者

5种实现方式配置Spring Boot API接口超时时间

图片
图片
图片
图片
图片
图片
图片
图片

【声明】内容源于网络
0
0
Spring全家桶实战案例
Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
内容 832
粉丝 0
Spring全家桶实战案例 Java全栈开发,前端Vue2/3全家桶;Spring, SpringBoot 2/3, Spring Cloud各种实战案例及源码解读
总阅读38
粉丝0
内容832