SpringValidation参数校验

111 阅读2分钟

Spring Validation

参考博客:SpringBoot 如何进行参数校验,老鸟们都这么玩的!-阿里云开发者社区 (aliyun.com)

入门 |验证表单输入 (spring.io)

*使用单参数校验时需要在Controller上加上@Validated注解,否则不生效

依赖引入

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

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

BindingResult

可以跟在需要校验的参数后面,如果参数校验失败,异常信息会被绑定到该接口上,不过异常不会被抛出,无法被全局异常所捕获到

	@GetMapping("/test")
    public R<Object> test(@NotBlank(message = "name不能为空") String name, BindingResult bindingResult) {
        List<String> list = null;
        if (bindingResult.hasErrors()) {
            list = bindingResult.getAllErrors().stream().map(ObjectError::toString).toList();
        }
        return R.success(list);
    }

全局异常处理

@ExceptionHandler(value = {MethodArgumentNotValidException.class, ValidationException.class, BindException.class})
    public R<String> validationException(Exception exception) {
        log.info("参数校验失败!");
        if (exception instanceof MethodArgumentNotValidException methodArgumentNotValidException) {
            return R.error(methodArgumentNotValidException.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining("; ")), "数据校验失败");
        } else if (exception instanceof ConstraintViolationException constraintViolationException) {
            return R.error(constraintViolationException.getConstraintViolations().stream().map(ConstraintViolation::getMessage).collect(Collectors.joining("; ")), "数据效验失败");
        } else if (exception instanceof BindException bindException) {
            return R.error(bindException.getMessage(), "数据效验失败");
        }
        return R.error(exception.getMessage(), "数据效验失败");
    }

自定义校验注解

自定义注解

不同的效验规则,可添加不同校验规则类实现:

@Constraint(validatedBy = {})

package com.example.portablefortheelderlybackground.config.validation;

import com.example.portablefortheelderlybackground.config.validation.Validated.isPhoneValidation;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @author 86187
 * @description: 自定义手机效验注解
 * @date: 2023/7/5 12:49
 */

@Target(value = {ElementType.FIELD, ElementType.PARAMETER})
@Retention(value = RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {isPhoneValidation.class})//效验的逻辑类
public @interface isPhone {
    String message() default "手机号不规范";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
    
    String value() default "手机号验证";
}

自定义注解逻辑的实现类

在校验类中实现ConstraintValidator<A,T>接口,A是具体的注解,T是注解添加的字段或者实体类型

并实现初始化和校验方法

其中初始化方法能够获取到注解的所有属性,如value(),groubs(),

校验方法中第一个参数会传进添加上字段的属性值,根据属性值进行判断

package com.example.portablefortheelderlybackground.config.validation.Validated;

import com.example.portablefortheelderlybackground.config.validation.isPhone;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

public class isPhoneValidation implements ConstraintValidator<isPhone, String> {
    //初始化,默认
    @Override
    public void initialize(isPhone constraintAnnotation) {
        String value=constraintAnnotation.value()
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        //参数s就是具体的值
        if (!StringUtils.hasLength(s))
            return false;
        return s.matches("^(?:(?:\\+|00)86)?1[3-9]\\d{9}$");
    }
}

自定义异常分组

需要用到spring提供的@Validated注解

注解源码


@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Validated {

	/**
	 * Specify one or more validation groups to apply to the validation step
	 * kicked off by this annotation.
	 * <p>JSR-303 defines validation groups as custom annotations which an application declares
	 * for the sole purpose of using them as type-safe group arguments, as implemented in
	 * {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}.
	 * <p>Other {@link org.springframework.validation.SmartValidator} implementations may
	 * support class arguments in other ways as well.
	 */
	Class<?>[] value() default {};

}

实现接口

分组的类通过接口实现

package com.example.mailthirdpart.common.validation;

public interface AddGroup {
}

添加分组

在需要校验的规则上,通过groups添加分组即可,

package com.example.mailthirdpart.pojo;

import com.example.mailthirdpart.common.validation.AddGroup;
import lombok.Data;

import javax.validation.constraints.NotBlank;

@Data
public class user {
    private String name;
    @NotBlank(message = "not blank", groups = {AddGroup.class})//添加效验分组
    private String id;
    private String sex;
    private String phone;
    private String password;
}

分组校验

这样,只对相应的分组所属字段进行校验

@GetMapping("/test")
    public R<Object> test(@Validated(value = {AddGroup.class}) @RequestBody user user, BindingResult bindingResult) {
        List<String> list = null;
        if (bindingResult.hasErrors()) {
            list = bindingResult.getAllErrors().stream().map(ObjectError::toString).toList();
        }
        return R.success(list);
    }