java后端参数校验validaction(用法详解)

1.前言

beanvalidation官网:https://beanvalidation.org/(是规范,api接口)

hibernate官网:https://hibernate.org/validator/(是beanvalidation的最佳实现)

添加依赖包

hibernate-validator 或jakarta.activation


    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>6.0.2.Final</version>
    </dependency>

    <!-- jakarta 规范版本,和javax版本内容一样,只是包名不同而已 -->
    <dependency>
      <groupId>jakarta.activation</groupId>
      <artifactId>jakarta.activation-api</artifactId>
      <version>2.0.1</version>
    </dependency>

springBoot项目可以引入这个:


     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-validation</artifactId>
         <version>1.4.0.RELEASE</version>
     </dependency><dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

校验提示信息


    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-el</artifactId>
      <version>9.0.29</version>
    </dependency>

2.初体验

ValidationUtil工具类

public class ValidationUtil {
    
    
    // 线程安全
    private static Validator validator ;
    static{
    
    
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    /**
     *  校验
     * @param userInfo 校验的对象
     * @return 校验失败的信息
     */
    public static List<String> valid(UserInfo userInfo){
    
    
        // 没有通过,则set里面就哟校验信息
        Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo);
        List<String> list = set.stream().map(v -> "属性:" + v.getPropertyPath()
                + " 属性值:" + v.getInvalidValue()
                + " 提示信息:" + v.getMessage()).collect(Collectors.toList());
        list.stream().forEach(System.out::println);
        return list;

    }
}

需要校验的Bean

public class UserInfo {
    
    

    private Long id;
    @Null
    private String name;

    private Integer age;

    private String email;

    private String phone;

    private LocalDateTime birthDay;

    private String personalPage;

    public Long getId() {
    
    
        return id;
    }

    public void setId(Long id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    public Integer getAge() {
    
    
        return age;
    }

    public void setAge(Integer age) {
    
    
        this.age = age;
    }

    public String getEmail() {
    
    
        return email;
    }

    public void setEmail(String email) {
    
    
        this.email = email;
    }

    public String getPhone() {
    
    
        return phone;
    }

    public void setPhone(String phone) {
    
    
        this.phone = phone;
    }

    public LocalDateTime getBirthDay() {
    
    
        return birthDay;
    }

    public void setBirthDay(LocalDateTime birthDay) {
    
    
        this.birthDay = birthDay;
    }

    public String getPersonalPage() {
    
    
        return personalPage;
    }

    public void setPersonalPage(String personalPage) {
    
    
        this.personalPage = personalPage;
    }

    @Override
    public String toString() {
    
    
        return "UserInfo{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", phone='" + phone + '\'' +
                ", birthDay=" + birthDay +
                ", personalPage='" + personalPage + '\'' +
                '}';
    }
}

校验

    @Test
    void test2(){
    
    
        UserInfo userInfo = new UserInfo();
        userInfo.setName(null);
        ValidationUtil.valid(userInfo);
    }

校验结果:

在这里插入图片描述

3.JSR校验注解

可以在javax.validation.constraints包下看到支持的所有注解

注解 注解作用说明
@Null 被注释的元素值必须为 null
@NotNull 被注释的元素值必须不为 null
@Pattern(regex=) 被注释的元素字符串必须符合指定的正则表达式
@Size(max=, min=) 集合元素数量必须在min和max范围内
@AssertTrue 被注释的元素必须为 true
@AssertFalse 被注释的元素必须为 false
@Min(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值(为null校验通过)
@Max(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值(为null校验通过
@Range(min,max) 数字必须在min和max范围内(为null校验通过)
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Digits (integer, fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past 被注释的元素必须是一个过去的日期
@Future 被注释的元素必须是一个将来的日期
@Email 字符串必须是Email地址
@SafeHtml 字符串必须是安全的html
@URL 字符串必须是合法的URL

| @Size(max,min) | 限制字符长度必须在min到max之间 |

4.Hibernate Validator校验注解:

可以在org.hibernate.validator.constraints包下看到支持的所有注解【该注解中有说明支持那些数据类型】,重复的我就省略掉了

注解 注解作用说明
@NotBlank(message =) 验证字符串非null,且trim后长度必须大于0
@Length(min=,max=) 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空(null,"")

| @Valid | 对bean实体类进行级联校验 |

注:每个注解的属性值中都有一个message属性,可以自己定义校验未通过消息

  @Min(value = 18,message = "年龄小于{value}岁,校验失败")
    private Integer age; // 大于等于18

// 属性:age 属性值:17 提示信息:年龄小于18岁,校验失败

5.分组校验

默认的组为:javax.validation.groups.Default

例如:我们一个字段Id,需要在不同的场景下,进行不同的校验,向下面这样的方式就行不可以的,需要进行一个分组

    @NotNull // 适应于修改
    @Null //适应与新增
    private Long id;

结局方案:使用分组校验 在实体类中定义需要分组的接口标记

public class UserInfo {
    
    
    // 新增组标记接口
    public interface UserInfoAdd{
    
    

    }
    // 修改组标记接口
    public interface UserInfoUpdate{
    
    

    }

    @NotNull(groups = {
    
    UserInfoUpdate.class}) // 适应于修改
    @Null(groups = {
    
    UserInfoAdd.class}) //适应与新增
    private Long id;
}

ValidationUtil中稍作修改,进行校验的时候,指定使用那个组进行校验,记得加上默认组,避免之前的校验规则失效

public class ValidationUtil {
    
    
    // 线程安全
    private static Validator validator ;
    static{
    
    
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    /**
     *  校验
     * @param userInfo 校验的对象
     * @return 校验失败的信息
     */
    public static  List<String> valid(UserInfo userInfo, Class<?> group){
    
    
        // 没有通过,则set里面就哟校验信息
        Set<ConstraintViolation<UserInfo>> set = validator.validate(userInfo, Default.class,group);
        List<String> list = set.stream().map(v -> "属性:" + v.getPropertyPath()
                + " 属性值:" + v.getInvalidValue()
                + " 提示信息:" + v.getMessage()
                + " 消息模板:"+v.getMessageTemplate()
        ).collect(Collectors.toList());
        list.stream().forEach(System.out::println);
        return list;

    }
}

6.级联校验

假设我们在UserInfo中有一个Grade属性然后设置@NotNull是可以校验成功的,但是我们Grade类本身字节有个number属性,也设置了@NotBlank,此时在进行校验的时候就不可以了,无法校验,此时就需要使用级联校验。只需要在引用的对象上加上 @Valid,被引用的对象中的校验规则也会随着生效

    @NotNull
    //在被引用对象上加上@Valid才可以完成级联校验
    @Valid
    private Grade grade;

7.自定义注解

假设我们现在业务中,有一个falg值来标识,表示我们的业务逻辑,比如说订单状态:1:未分配,2:已分配,3:处理完成,这样的话,显然原有的功能无法满足我们的需求,我们可以使用自定义注解来完成。

1.先自定义注解

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-11-08-17:49
 * @Version:1.0
 * @Description: 自定义注解:被标注的字段属性值必须是:1,2,3其中一个
 */
@Target({
    
      FIELD })
@Retention(RUNTIME)
@Documented
// 说明当前注解@UserInfoFlag要被谁来完成校验工作
@Constraint(validatedBy = {
    
     FlagValidation.class})
public @interface UserInfoFlag {
    
    

    String message() default "校验不通过,flag不符合要求";

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

    Class<? extends Payload>[] payload() default {
    
     };
}

2.对自定义注解的校验功能实现


/**
 * Created with IntelliJ IDEA.
 *
 * @Author: compass
 * @Date: 2021-11-08-17:47
 * @Version:1.0
 * @Description: 自定义注解
 */
//ConstraintValidator<自定义的注解标识,需要处理的数据类型>
public class FlagValidation implements ConstraintValidator<UserInfoFlag,String> {
    
    

    @Override
    public void initialize(UserInfoFlag constraintAnnotation) {
    
    

    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
    
    
        // 新建一个集合,放入1,2,3 如果如果集合中未包含value就校验失败
        Set<String> set = new HashSet<>();
        set.add("1");
        set.add("2");
        set.add("3");
        return set.contains(value);
    }
}

使用:在需要校验的字段上加上 @UserInfoFlag 注解即可

    @UserInfoFlag(message = "校验失败,flag必须是 1,2,3中的一个")
    private String flag;

8.快速失败校验

    /**
     *  只要有一个校验失败,立刻返回结果,生效的不予校验
     * @param userInfo 校验的对象
     * @return 校验失败的信息
     */
    public static <c> List<String> validFastFail(UserInfo userInfo, Class<?> group){
    
    

        // 没有通过,则set里面就哟校验信息                              [加上默认组,避免之前的校验规则失效]
        Set<ConstraintViolation<UserInfo>> set = failFast.validate(userInfo, Default.class,group);
        List<String> list = set.stream().map(v -> "属性:" + v.getPropertyPath()
                + " 属性值:" + v.getInvalidValue()
                + " 提示信息:" + v.getMessage()
                + " 消息模板:"+v.getMessageTemplate()
        ).collect(Collectors.toList());
        list.stream().forEach(System.out::println);
        return list;

    }

9.非Bean入参校验

修改一下校验方法,使用参数校验的模式


    /**
     * 非bean校验入参
     * @param object 校验的对象
     * @param method 校验的方法
     * @param parameterValues 校验参数值
     * @param groups 使用那个组进行校验
     * @param <T>
     * @return 校验结果
     */
    public static <T> List<String> validNotBean(T object,
                                                Method method,
                                                Object[] parameterValues,
                                                Class<?>... groups){
    
    
Set<ConstraintViolation<T>> set = executableValidator.validateParameters(object, method, parameterValues, groups);

        List<String> list = set.stream().map(v -> "属性:" + v.getPropertyPath()
                + " 属性值:" + v.getInvalidValue()
                + " 提示信息:" + v.getMessage()
        ).collect(Collectors.toList());
        list.stream().forEach(System.out::println);

        return list;
    }

测试方法

public class UserInfoService {
    
    
    public String getByName(@NotNull String name){
    
    
        String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
        Method method =null;
        try {
    
    
             method = this.getClass().getDeclaredMethod(methodName, String.class);
        }catch (Exception e){
    
    

        }
        ValidationUtil.validNotBean(this,method,new Object[]{
    
    name});
        return "admin";
    }
}

10spring mvc vaildaction校验

1.使用@Valid校验:@Valid的缺点,就是不能进行分组校验

springMVC之中已经提供了一系列的自动校验,我们只需要在需要校验的地方加上对应的注解即可

@RestController
public class UserController {
    
    

    @GetMapping("/addUser")
    public  Map<String, String> addUser(@Valid UserInfo userInfo, BindingResult errorResult){
    
    
        Map<String, String> map = new HashMap<>();
        if (errorResult.hasErrors()){
    
    
           // 获取出错的对象
           List<ObjectError> errorList = errorResult.getAllErrors();
           // 获取到出错的字段
           List<FieldError> errors = errorResult.getFieldErrors();
           StringBuffer errorMessage=new StringBuffer();
           for (FieldError error : errors) {
    
    
               System.out.println("不通过的属性:"+error.getField()+
                       " 不通过的属性值:"+error.getRejectedValue()+
                       " 错误原因"+error.getDefaultMessage());
               errorMessage.append(" 不通过的属性:"+error.getField()+",");
               errorMessage.append(" 错误原因"+error.getDefaultMessage()+",");
               errorMessage.append(" 不通过的属性值:"+error.getRejectedValue());
               errorMessage.append(";");
           }
           map.put("error",errorMessage.toString());

       }else {
    
    
            map.put("success","校验成功");
        }

        return map;
    }
}

测试结果:浏览器中输入: http://localhost:8888/addUser 进行访问

在这里插入图片描述

2.使用@Validated完成分组校验

@GetMapping("/addUser2")
    public  Map<String, String> addUser2(@Validated(value = {
    
    UserInfo.UserInfoAdd.class, Default.class})
                                                     UserInfo userInfo, BindingResult errorResult){
    
    
        Map<String, String> map = new HashMap<>();
        if (errorResult.hasErrors()){
    
    
            // 获取出错的对象
            List<ObjectError> errorList = errorResult.getAllErrors();
            // 获取到出错的字段
            List<FieldError> errors = errorResult.getFieldErrors();
            StringBuffer errorMessage=new StringBuffer();
            for (FieldError error : errors) {
    
    
                System.out.println("不通过的属性:"+error.getField()+
                        " 不通过的属性值:"+error.getRejectedValue()+
                        " 错误原因"+error.getDefaultMessage());
                errorMessage.append(" 不通过的属性:"+error.getField()+",");
                errorMessage.append(" 错误原因"+error.getDefaultMessage()+",");
                errorMessage.append(" 不通过的属性值:"+error.getRejectedValue());
                errorMessage.append(";");
            }
            map.put("error",errorMessage.toString());

        }else {
    
    
            map.put("success","校验成功");
        }
        return map;
    }

3.全局异常处理

在每个方法中都添加BindingResult去处理是非常麻烦的,我们可以添加一个全局异常处理器,去处理我们的参数校验,这样我们的请求方法的参数就不需要在绑定BindingResult

@ControllerAdvice
public class ControllerExceptionAdvice {
    
    

    @ExceptionHandler(BindException.class)
    @ResponseBody
    public Map<String, String> handlerException(BindException e) {
    
    
        Map<String, String> map = new HashMap<>();
        if (e.getFieldErrors().size() > 0) {
    
    
            List<FieldError> errors = e.getFieldErrors();
            StringBuffer errorMessage = new StringBuffer();
            for (FieldError error : errors) {
    
    
                System.out.println("不通过的属性:" + error.getField() +
                        " 不通过的属性值:" + error.getRejectedValue() +
                        " 错误原因" + error.getDefaultMessage());
                errorMessage.append(" 不通过的属性:" + error.getField() + ",");
                errorMessage.append(" 错误原因" + error.getDefaultMessage() + ",");
                errorMessage.append(" 不通过的属性值:" + error.getRejectedValue());
                errorMessage.append(";");
            }
            map.put("error", errorMessage.toString());
        } else {
    
    
            map.put("success", "校验成功");
        }
        return map;
    }

}

相比上面的代码,以下代码都简单多了,因为如果只要出现BindException异常,就会被我们的ControllerExceptionAdvice中的handlerException方法锁处理,并且返回我们的错误信息


    @GetMapping("/addUser3")
    public  void addUser3(@Validated(value = {
    
    UserInfo.UserInfoAdd.class, Default.class})
                                                 UserInfo userInfo){
    
    
        Map<String, String> map = new HashMap<>();

    }

补充:@Validated写在类上的时候,表示整个类都启用校验,如果碰到bea validation中的注解的话,就会自动校验,如果校验失败就会抛出ConstraintViolationException异常

Controller类中加上@Validated注解即可,在springMVC的web环境下,默认不是快速失败校验,如果有一个参数校验不通过,还是会继续校验,并且如果校验失败,Controller中的代码是不会执行的,会直接由全局异常处理器返回处理结果

  • @Valid是使用Hibernate validation的时候使用
  • @Validated是只用Spring Validator校验机制使用
    @GetMapping("/getName")
    public  void getName(@NotNull String name,@NotNull@Length(min = 3,max = 10) String address){
    
    

    }

ControllerExceptionAdvice类中捕获ConstraintViolationException异常并且做出处理,需要注意的是这些注解功能是单一的,有的注解不能判断为空操作,如果需要判断则需要添加额外的注解进行处理

//  表示整个类都启用校验,如果碰到bea validation中的注解的话,就会自动校验,如果校验失败就会抛出ConstraintViolationException
    @ExceptionHandler(ConstraintViolationException.class)
    @ResponseBody
    public Map<String, String> violationException(ConstraintViolationException e) {
    
    
        Map<String, String> map = new HashMap<>();
        Set<ConstraintViolation<?>> set = e.getConstraintViolations();

        if (set.size() > 0) {
    
    
            List<String> list = set.stream().map(v -> "属性名:"
                    + v.getPropertyPath().toString().substring(v.getPropertyPath().toString().indexOf(".")+1)
                    + " 属性值:" + v.getInvalidValue()
                    + " 提示信息:" + v.getMessage()).collect(Collectors.toList());
            StringBuffer errorMessage = new StringBuffer();
            for (int i=0;i<list.size();i++){
    
    
                errorMessage.append(list.get(i)+";");
            }
            map.put("error", errorMessage.toString());
        } else {
    
    
            map.put("success", "校验成功");
        }
        return map;
    }

运行结果:

String> list = set.stream().map(v -> "属性名:"
                    + v.getPropertyPath().toString().substring(v.getPropertyPath().toString().indexOf(".")+1)
                    + " 属性值:" + v.getInvalidValue()
                    + " 提示信息:" + v.getMessage()).collect(Collectors.toList());
            StringBuffer errorMessage = new StringBuffer();
            for (int i=0;i<list.size();i++){
    
    
                errorMessage.append(list.get(i)+";");
            }
            map.put("error", errorMessage.toString());
        } else {
    
    
            map.put("success", "校验成功");
        }
        return map;
    }

运行结果:

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/m0_46188681/article/details/121217856