Spring利用@Validated注解实现参数校验详解

作者:蜀山剑客李沐白 时间:2023-08-26 12:06:26 

Spring Framework 提供了一套可以方便地对 Controller 层中接收的参数进行校验的框架,其中就包括了 @Validated 注解。在 Spring 项目中使用 @Validated 注解可以让我们更加方便地进行参数校验,避免了手动校验的麻烦,并且使得代码更加优雅和易于维护。本文将详细介绍在 Spring 项目中使用 @Validated 进行参数校验的方法和常见应用场景。

一、@Validated 注解简介

@Validated 注解是 Spring Framework 中提供的一个参数校验注解,它可以用来标记需要进行参数校验的方法、类、方法参数和方法返回值等地方。通过使用 @Validated 注解,我们可以非常方便地对入参进行检查,并且可以自定义校验规则和错误提示信息。

引入依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

二、@Validated 注解的使用

2.1 在 Controller 层中使用

在 Controller 层中使用 @Validated 注解是最为常见的使用场景。通过在 Controller 方法的参数上添加 @Validated 注解,可以对该参数进行校验。下面是一个简单的例子:

@RestController
@RequestMapping("/api/user")
public class UserController {
   @PostMapping
   public User createUser(@RequestBody @Validated User user) {
       // ...
   }
}

在上述代码中,@Validated 注解标记了 User 类型的参数,表示需要对该参数进行校验。如果 User 类中存在校验注解,那么这些注解会自动触发校验过程。如果校验不通过,则会抛出 MethodArgumentNotValidException 异常。

2.2 自定义校验规则

在使用 @Validated 注解进行参数校验时,我们常常需要自定义校验规则。Spring Framework 提供了多种自定义校验规则的方式,包括使用注解实现、编写自定义 Validator 等方式。

2.2.1 使用注解实现

可以通过编写注解来进行自定义校验规则的实现。例如,下面是一个用于校验手机号码格式的注解:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = PhoneValidator.class)
public @interface Phone {
   String message() default "手机号码格式不正确";
   Class<?>[] groups() default {};
   Class<? extends Payload>[] payload() default {};
}

在上述代码中,@Phone 注解用于标记需要进行手机号码格式校验的字段,而 @Constraint 注解指定了具体的校验逻辑类 PhoneValidator。PhoneValidator 实现了 ConstraintValidator 接口,完成对手机号码格式的校验。

接下来,我们来看一下 PhoneValidator 的实现:

public class PhoneValidator implements ConstraintValidator<Phone, String> {
   private final static Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
   @Override
   public void initialize(Phone constraintAnnotation) {
   }
   @Override
   public boolean isValid(String phone, ConstraintValidatorContext context) {
       return StringUtils.isEmpty(phone) || PHONE_PATTERN.matcher(phone).matches();
   }
}

在 PhoneValidator 的 isValid 方法中,我们使用了正则表达式来判断手机号码的格式是否正确。如果格式不正确,则返回 false,并且可以通过 context 参数来设置错误提示信息。

使用上述自定义注解实现的校验规则,可以和 Spring 自带的校验注解一样,方便地被应用到 Controller 层中。

2.2.2 编写自定义 Validator

除了使用注解来实现自定义校验规则以外,还可以编写自定义 Validator 来实现具体的校验逻辑。

下面是一个简单的示例,用于校验两个整数的大小关系:

public class ComparisonValidator implements ConstraintValidator<Comparison, Object> {
   private Comparison.Operator operator;
   private String valueFieldName;
   @Override
   public void initialize(Comparison constraintAnnotation) {
       operator = constraintAnnotation.operator();
       valueFieldName = constraintAnnotation.valueFieldName();
   }
   @Override
   public boolean isValid(Object value, ConstraintValidatorContext context) {
       if (value == null) {
           return true;
       }
       try {
           Field valueField = value.getClass().getDeclaredField(valueFieldName);
           valueField.setAccessible(true);
           Object otherValue = valueField.get(value);
           if (otherValue == null) {
               return true;
           }
           int result = ((Comparable) value).compareTo(otherValue);
           switch (operator) {
               case GREATER_THAN:
                   return result > 0;
               case LESS_THAN:
                   return result < 0;
               case GREATER_THAN_OR_EQUAL_TO:
                   return result >= 0;
               case LESS_THAN_OR_EQUAL_TO:
                   return result <= 0;
               default:
                   throw new IllegalArgumentException("Unsupported Comparison Operator: " + operator);
           }
       } catch (NoSuchFieldException | IllegalAccessException e) {
           throw new RuntimeException(e);
       }
   }
}

在上述代码中,我们定义了一个 ComparisonValidator 类,它实现了 ConstraintValidator 接口,并且自定义了一个校验规则 @Comparison。

该校验规则需要通过 operator 和 valueFieldName 两个属性来确定具体的比较方式和被比较的属性名称。在 isValid 方法中,我们首先获取到被比较的属性值 otherValue,然后根据 operator 来判断 value 是否大于(小于、等于)otherValue。

使用自定义 Validator 需要手动创建校验逻辑类,并将其与注解进行关联。在 Controller 层使用时,我们可以像使用 Spring 自带的校验注解一样来使用自定义的校验注解。

2.3 组合注解

有时候,我们需要对同一个参数进行多种校验,这时候可以使用组合注解的方式来实现。例如,下面是一个用于校验密码格式的组合注解示例:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {})
@Pattern(regexp = "^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[@#$%^&+=])(?=\\S+$).{8,}$", message = "密码必须包含大小写字母、数字和特殊字符,长度至少为 8 位")
@NotNull(message = "密码不能为空")
public @interface Password {
   String message() default "密码格式不正确";
   Class<?>[] groups() default {};
   Class<? extends Payload>[] payload() default {};
}

在上述代码中,@Password 注解通过组合 @Pattern 和 @NotNull 注解来实现了密码的格式校验。如果密码格式不正确或者为空,则会抛出校验异常。
在使用组合注解时,需要注意被组合的注解是否已经使用了 @Constraint 注解,并且不要忘记设置 message、groups 和 payload 等属性。

2.4 统一异常处理

在使用 @Validated 进行参数校验时,如果校验失败,会抛出 MethodArgumentNotValidException 异常。为了提高代码的可维护性,我们可以通过在 Controller 层添加 @ExceptionHandler 注解并捕获该异常,来统一处理校验失败的情况。

例如,下面是一个简单的异常处理示例:

@RestControllerAdvice
public class GlobalExceptionHandler {
   @ExceptionHandler(MethodArgumentNotValidException.class)
   public Map<String, Object> handleValidationException(MethodArgumentNotValidException ex) {
       BindingResult bindingResult = ex.getBindingResult();
       List<String> errorList = bindingResult.getAllErrors().stream()
               .map(DefaultMessageSourceResolvable::getDefaultMessage)
               .collect(Collectors.toList());
       return Collections.singletonMap("message", errorList);
   }
}

在上述代码中,我们通过在 GlobalExceptionHandler 类中添加 @RestControllerAdvice 和 @ExceptionHandler 注解来统一处理 MethodArgumentNotValidException 异常。在 handleValidationException 方法中,我们首先获取到 BindingResult 对象,并通过遍历所有错误信息来收集错误提示信息。最终返回一个包含错误提示信息的 Map 对象。

三、@Validated 注解的常见应用场景

@Validated 注解作为 Spring Framework 中的一个参数校验注解,广泛应用于 Controller 层的参数校验、DTO 类的参数校验和业务类的参数校验等方面。下面列举了几个常见的应用场景:

1.数据库操作时的参数校验:数据库操作一般需要对参数进行校验,避免因为无效参数导致的 SQL 注入等安全问题。

2.DTO 类的参数校验:在使用 DTO(Data Transfer Object)类进行数据传输时,往往需要对传输的字段进行校验,保证数据的有效性和完整性。

3.业务类的参数校验:业务类中的方法通常也需要对参数进行校验,以确保业务逻辑的正确性和可靠性。

四、常用的验证注解

1.@NotNull 注解

@NotNull 表示被注解的参数不能为 null。

例如:

public void testNotNull(@NotNull String str) {}

2.@Size 注解

@Size 表示被注解的参数的大小必须在指定的范围内(包括最小值和最大值)。

例如:

public void testSize(@Size(min = 1, max = 10) String str) {}

3.@Min 和 @Max 注解

@Min 和 @Max 分别表示被注解的参数的最小值和最大值。

例如:

public void testMin(@Min(18) int age) {}
public void testMax(@Max(100) int score) {}

4.@DecimalMin 和 @DecimalMax 注解

@DecimalMin 和 @DecimalMax 分别表示被注解的参数的最小值和最大值,适用于浮点数、BigDecimal 或 BigInteger 类型的参数。

例如:

public void testDecimalMin(@DecimalMin("0.00") BigDecimal price) {}
public void testDecimalMax(@DecimalMax("100.00") BigDecimal score) {}

5.@Digits 注解

@Digits 表示被注解的参数必须是一个数字,并且整数位和小数位的位数不能超过指定的值(默认整数位 2 位,小数位 0 位)。

例如:

public void testDigits(@Digits(integer = 2, fraction = 1) BigDecimal num) {}

6.@Email 注解

@Email 表示被注解的参数必须是一个合法的电子邮件地址。

例如:

public void testEmail(@Email String email) {}

7.@Pattern 注解

@Pattern 表示被注解的参数必须符合指定的正则表达式模式。

例如:

public void testPattern(@Pattern(regexp = "^\\d{4}-\\d{1,2}-\\d{1,2}$") String date) {}

除了使用单个注解外,也可以使用组合注解来完成更为复杂的校验逻辑。

例如:

@NotNull(message = "用户名不能为空")
@Size(min = 5, max = 20, message = "用户名长度必须在 5 到 20 之间")
public String getUsername() {
   return this.username;
}

上述代码表示要求用户名不能为空,并且在长度范围内。如果不符合要求,则会抛出相应的异常,如 MethodArgumentNotValidException 等。

来源:https://juejin.cn/post/7236654133020983357

标签:Spring,Validated
0
投稿

猜你喜欢

  • C# DataTable与Model互转的示例代码

    2022-02-07 19:37:44
  • C#自定义特性(Attribute)详解

    2023-12-13 12:51:08
  • 浅谈Java中IO和NIO的本质和区别

    2023-11-01 01:44:08
  • Flutter 使用fluro的转场动画进行页面切换

    2023-06-17 11:49:26
  • winform实现创建最前端窗体的方法

    2023-07-12 09:50:43
  • 使用vscode搭建javaweb项目的详细步骤

    2022-08-05 01:12:43
  • Android Dispatchers.IO线程池深入刨析

    2022-12-13 16:20:31
  • C#异步委托调用实例分析

    2022-12-14 05:41:06
  • Android基于opencv实现多通道分离与合并

    2022-05-04 09:49:29
  • 一文搞懂Android RecyclerView点击展开、折叠效果的实现代码

    2023-02-17 14:52:24
  • C++中cin的返回值问题

    2021-07-04 17:59:22
  • Android中使用 AutoCompleteTextView 实现手机号格式化附带清空历史的操作

    2021-07-05 17:08:43
  • Dubbo实现分布式日志链路追踪

    2023-08-23 21:00:54
  • Spring Boot 自动配置的实现

    2023-07-21 18:18:55
  • 使用mybatis-plus想要修改某字段为null问题

    2021-09-17 02:17:35
  • Android自定义实现图片加文字功能

    2022-08-03 22:05:49
  • Android 图片切换器(dp、sp、px) 的单位转换器

    2022-04-25 02:48:06
  • C#利用性能计数器监控网络状态

    2022-01-05 00:13:53
  • Android如何自定义EditText下划线?

    2022-06-27 10:49:07
  • Android Studio使用Kotlin时,修改代码后运行不生效的解决方法

    2022-08-05 11:29:04
  • asp之家 软件编程 m.aspxhome.com