bean-validation----hibernate-validator校验框架整理【未完待续】

代码简洁之道 beanvalidationhibernate-validator

序言

beanvalidation官网:https://beanvalidation.org/ api的接口

Hibernate-validator官网:http://hibernate.org/validator/ api的实现

image-20220906215857367

一、传统的参数校验

import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户信息
 *
 * @author zs
 * @date 2022-09-06
 */
@Data
public class UserInfo {
    private Long id;
    /**
     * 不能是null, "", "   "
     */
    private String name;
    /**
     * 正整数, 1-800
     */
    private Integer age;
    /**
     * email的格式
     */
    private String email;
    /**
     * 符合中国大陆手机号
     */
    private String phone;
    /**
     * 不能超过当前日期
     */
    private LocalDateTime birthday;
    /**
     * url
     */
    private String personalPage;
}
public class TraditionalTest {


    @Test
    public void test01(){
        UserInfo userInfo = new UserInfo();
        validateUserInfo(userInfo);
    }

    private static void validateUserInfo(UserInfo userInfo){
        // 用户名校验
        String name = userInfo.getName();
        if (name == null || "".equals(name) || "".equals(name.trim())) {
            //不符合校验规则
            throw new RuntimeException("name 不符合校验规则");
        }

        // age校验
        Integer age = userInfo.getAge();
        boolean ageValidate = age > 1 && age < 800;
        if (!ageValidate) {
            throw new RuntimeException("age不符合校验规则,应在(1-800)");
        }

        //......
    }

}

二、javaEE规范的故事

  • 是什么?
    • 不相关的很多java package组成了javaee规范
  • 在哪里?
    • javax开头的包
    • javax.sql ---- mysql ,sqlserver,oracle …
    • Javax.jms ---- activemq
    • Javax.servlet ---- tomcat,jetty …
    • Javax.persistence ---- hibernate
    • Javax.transaction----分布式事务
    • javax.xml----jaxp: java api for xml processing

​ jdk自带了一些常用的javaee规范,对于没有自带的如果想要使用就需要自己引用了,比如beanvalidation

  • 如何制定?

    jcp官网:https://jcp.org/en/home/index

    Jsr:JavaSpecification Requests java规范提案, 如beanvalidation的提案有如下3个。

    • 由谁:jcp里的成员:https://jcp.org/en/participation/members
    提案号beanvalidation版本
    jsr303beanvalidation1.0
    jsr349beanvalidation1.1
    Jsr380beanvalidation2.0
  • javax走向jakarta

    参考:https://blogs.oracle.com/theaquarium/opening-up-java-ee oracle把javaee规范捐献给eclipse基金会

  • 不要和Apache的jakarta混为一谈

    2011退休了:https://jakarta.apache.org/

三、非web环境下使用校验

3.1 搭建环境

        <!--引入beanvalidation:hibernate-validator会依赖所以省略引入-->
<!--        &lt;!&ndash;javax 的javaee规范版本&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>javax.validation</groupId>-->
<!--            <artifactId>validation-api</artifactId>-->
<!--            <version>2.0.1.Final</version>-->
<!--        </dependency>-->
<!--        &lt;!&ndash;jakarta 规范的版本,和javax版本内容一样,就是包名不同&ndash;&gt;-->
<!--        <dependency>-->
<!--            <groupId>jakarta.validation</groupId>-->
<!--            <artifactId>jakarta.validation-api</artifactId>-->
<!--            <version>2.0.2</version>-->
<!--        </dependency>-->


        <!--引入hibernate-validator-->
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>7.0.1.Final</version>
        </dependency>
        <!--el 规范和Tomcat的实现,用于解析messages里面的表达式-->
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-el</artifactId>
            <version>10.0.22</version>
        </dependency>

3.2 validator初体验

package com.zs.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ValidationUtil {

    //线程安全的 http://www.360doc.com/content/16/0222/17/16926569_536478632.shtml
    private static Validator validator;

    static {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    public static List<String>  valid(Object obj) {
        //如果被校验对象 没有校验通过,则set里面就有校验信息
        Set<ConstraintViolation<Object>> set = validator.validate(obj);
        List<String> list = set.stream().map(v -> "属性:" + v.getPropertyPath() + ",属性的值" +
                v.getInvalidValue() + ",校验不通过的提示信息:" + v.getMessage())
                .collect(Collectors.toList());
        return list;
    }
}

添加注解

import jakarta.validation.constraints.NotNull;

@Data
public class UserInfo {
    /**
     * 不能是null, "", "   "
     */
    @NotNull
    private String name;

测试

    @Test
    public void test01(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(2);
//        userInfo.setName("zs");
//        validateUserInfo(userInfo);

        List<String> list = ValidationUtil.valid(userInfo);
        System.out.println(list);
    }

[属性:name,属性的值null,校验不通过的提示信息:不能为null]

3.3 validato加载原理

spi机制

image-20220907002849439

未命名绘图

image-20220907004806321

3.4 常用的校验约束注解

  1. Bean Validation中内置的 constraint
@Null			被注释的元素必须为null
@NotNull	被注释的元素必须不为null
@NotEmpty	被注释的集合(size > 0)/字符串(!=null && !"")
@NotBlank	!=null && !"" && !"   "

@AssertTrue		被注释的元素必须为true
@AssertFalse	被注释的元素必须为false
@Min(value)		被注释的元素必须是一个数字,>=
@Max(value)		被注释的元素必须是一个数字,<=
@DecimalMin(value)		>=
@DecimalMax(value)		<=

@Size(max,min)		被注释的元素的大小必须在指定的范围内
@Digits(integer,fraction)	被注释的元素必须是一个数字,其值必须在可接受的范围内
@Past							被注释的元素必须是一个过去的日期
@PastOrPresent		时间
@NegativeOrZero		<=0
@Future						被注释的元素必须是一个将来的日期
@Pattern(value)		被注释的元素必须符合指定的正则表达式
@Email						被注释的元素必须是电子邮箱地址
image-20220907010014875
  1. Hibernate Validator附加的 constraint: org.hibernate.validator.constraints
@Length	被注释的字符串的大小必须在指定的范围内
@Range	被注释的元素必须在适合的范围内
@URL		一个url
image-20220907010315899
    /**
     * 不能是null, "", "   "
     */
    @NotBlank
//    @NotNull
//    @NotEmpty
    private String name;
    /**
     * 正整数, 1-800
     */
//    @Min(1) @Max(800)
     @Range(min=1,max=800)
    private Integer age;
/**
 * email的格式
 */
@NotBlank
@Email
private String email;
/**
 * 符合中国大陆手机号
 */
@Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$")
private String phone;
/**
 * 不能超过当前日期
 */
@NotNull
@Past
private LocalDateTime birthday;
/**
 * url
 */
@URL
private String personalPage;
package com.zs.validation.bean;


import jakarta.validation.constraints.*;
import lombok.Data;
import org.hibernate.validator.constraints.Range;
import org.hibernate.validator.constraints.URL;

import java.time.LocalDateTime;

/**
 * 用户信息
 *
 * @author zs
 * @date 2022-09-06
 */
@Data
public class UserInfo {
    private Long id;
    /**
     * 不能是null, "", "   "
     */
    @NotBlank
//    @NotNull
//    @NotEmpty
    private String name;
    /**
     * 正整数, 1-800
     */
//    @Min(1) @Max(800)
     @Range(min=1,max=800)
    private Integer age;
    /**
     * email的格式
     */
    @NotBlank
    @Email
    private String email;
    /**
     * 符合中国大陆手机号
     */
    @Pattern(regexp = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$")
    private String phone;
    /**
     * 不能超过当前日期
     */
    @NotNull
    @Past
    private LocalDateTime birthday;
    /**
     * url
     */
    @URL
    private String personalPage;
}

测试

package com.zs.validation;

import com.zs.validation.bean.UserInfo;
import org.junit.Test;

import java.time.LocalDateTime;
import java.util.List;

public class TraditionalTest {


    @Test
    public void test01(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(1);
        userInfo.setName("zs");
        userInfo.setEmail("aaaa@qq.com");
        userInfo.setPhone("13516466501");
        userInfo.setBirthday(LocalDateTime.now().minusDays(1));
        userInfo.setPersonalPage("http://www.baidu.com");
//        validateUserInfo(userInfo);

        List<String> list = ValidationUtil.valid(userInfo);
        System.out.println(list);
    }

    private static void validateUserInfo(UserInfo userInfo){
        // 用户名校验
        String name = userInfo.getName();
        if (name == null || "".equals(name) || "".equals(name.trim())) {
            //不符合校验规则
            throw new RuntimeException("name 不符合校验规则");
        }

        // age校验
        Integer age = userInfo.getAge();
        boolean ageValidate = age > 1 && age < 800;
        if (!ageValidate) {
            throw new RuntimeException("age不符合校验规则,应在(1-800)");
        }

        //......
    }

}

3.5 约束和校验类的绑定原理

@NotNull

NotNullValidator 类校验 NotNull注解

同理 @Xxx注解的校验器类为XxxValidator

org.hibernate.validator.internal.metadata.core.ConstraintHelper

if (enabledBuiltinConstraints.contains(BuiltinConstraint.JAKARTA_VALIDATION_CONSTRAINTS_NOT_BLANK)) {
    putBuiltinConstraint(tmpConstraints, NotBlank.class, NotBlankValidator.class);
}
private boolean isBuiltinConstraint(Class<? extends Annotation> annotationType) {
    return BuiltinConstraint.isBuiltin(annotationType.getName());
}

注意:一个注解约束可能对应多个约束Validator,如@NotEmpty

image-20220907224014674

3.6 自定义校验信息

@NotBlank(message = "你的名字不能为空")
private String name;

@Min(value = 18,message = "你的名字小于{value},禁止进入")
private Integer age;
public static List<String>  valid(Object obj) {
    //如果被校验对象 没有校验通过,则set里面就有校验信息
    Set<ConstraintViolation<Object>> set = validator.validate(obj);
    List<String> list = set.stream().map(v -> 
                    "属性:" + v.getPropertyPath() + 
                    ",属性的值" + v.getInvalidValue() + 
                    ",校验不通过的提示信息:" + v.getMessage() +
                    ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
    )
            .collect(Collectors.toList());
    return list;
}

3.7 分组校验

@Data
public class UserInfo {
    public interface Add { }
    public interface Update { }

    // 默认的组: jakarta.validation.groups.Default
    @Null(groups = {Add.class, Default.class}) // 只用于新增
    @NotNull(groups = Update.class)// 用于修改
    private Long id;

    @NotBlank(message = "你的名字不能为空")
    private String name;
package com.zs.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ValidationUtil {

    //线程安全的 http://www.360doc.com/content/16/0222/17/16926569_536478632.shtml
    private static Validator validator;

    static {
        validator = Validation.buildDefaultValidatorFactory().getValidator();
    }

    public static List<String>  valid(Object obj,Class<?>... groups) {
        //如果被校验对象 没有校验通过,则set里面就有校验信息
        Set<ConstraintViolation<Object>> set = validator.validate(obj,groups);
        List<String> list = set.stream().map(v ->
                        "属性:" + v.getPropertyPath() +
                        ",属性的值" + v.getInvalidValue() +
                        ",校验不通过的提示信息:" + v.getMessage() +
                        ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
        )
                .collect(Collectors.toList());
        return list;
    }
}

测试

@Test
public void test01(){
    UserInfo userInfo = new UserInfo();
    userInfo.setAge(1);
    userInfo.setName("zs");
    userInfo.setEmail("aaaa@qq.com");
    userInfo.setPhone("13516466501");
    userInfo.setBirthday(LocalDateTime.now().minusDays(1));
    userInfo.setPersonalPage("http://www.baidu.com");
    
    List<String> list = ValidationUtil.valid(userInfo, UserInfo.Add.class, Default.class);
    System.out.println(list);
}

3.8 @Valid级联校验

import jakarta.validation.constraints.NotBlank;
import lombok.Data;

@Data
public class Grade {
    
    //班级号
    @NotBlank
    private String no;
}
    @NotNull
    @Valid //被引用对象加@Valid注解才可以完成级联校验
    private Grade grade;
}
userInfo.setGrade(new Grade());
List<String> list = ValidationUtil.valid(userInfo, UserInfo.Add.class, Default.class);

3.9 自定义校验规则

注解

package com.zs.validation.annotation;

import jakarta.validation.Constraint;
import jakarta.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;

@Documented
@Constraint(validatedBy = {UserStatusValidator.class })//指定当前注解要被谁来完成校验工作
@Target({FIELD})
@Retention(RUNTIME)
public @interface UserStatus {

    String message() default "{userStatus必须是范围内的值}";

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

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

注解校验器

public class UserStatusValidator implements ConstraintValidator<UserStatus, String> {

    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if (s == null) {
            return true;
        } else {
            Set<String> set = new HashSet<>();
            set.add("10");
            set.add("20");
            set.add("30");
            return set.contains(s);
        }
    }

    @Override
    public void initialize(UserStatus constraintAnnotation) {

    }
}

使用

@UserStatus
private String status;

测试

    @Test
    public void test01(){
        UserInfo userInfo = new UserInfo();
        userInfo.setAge(19);
        userInfo.setName("zs");
        userInfo.setEmail("aaaa@qq.com");
        userInfo.setPhone("13516466501");
        userInfo.setBirthday(LocalDateTime.now().minusDays(1));
        userInfo.setPersonalPage("http://www.baidu.com");
        userInfo.setGrade(new Grade().setNo("11"));
        userInfo.setStatus("10");
        List<String> list = ValidationUtil.valid(userInfo, UserInfo.Add.class, Default.class);
        System.out.println(list);
    }

3.10 快速校验failfast

package com.zs.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import org.hibernate.validator.HibernateValidator;

import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ValidationUtil {

    //线程安全的 http://www.360doc.com/content/16/0222/17/16926569_536478632.shtml
    private static Validator validator;
    private static Validator failFastValidator;

    static {
        //默认校验器
        validator = Validation.buildDefaultValidatorFactory().getValidator();
        //快速失败的校验器
        failFastValidator = Validation.byProvider(HibernateValidator.class)
                .configure().failFast(true) //配置快速失败
                .buildValidatorFactory().getValidator();
    }

    public static List<String>  valid(Object obj,Class<?>... groups) {
        //如果被校验对象 没有校验通过,则set里面就有校验信息
        Set<ConstraintViolation<Object>> set = validator.validate(obj,groups);
        List<String> list = set.stream().map(v ->
                        "属性:" + v.getPropertyPath() +
                        ",属性的值" + v.getInvalidValue() +
                        ",校验不通过的提示信息:" + v.getMessage() +
                        ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
        )
                .collect(Collectors.toList());
        return list;
    }


    public static List<String>  validFailFast(Object obj,Class<?>... groups) {
        //如果被校验对象 没有校验通过,则set里面就有校验信息
        Set<ConstraintViolation<Object>> set = failFastValidator.validate(obj,groups);
        List<String> list = set.stream().map(v ->
                "属性:" + v.getPropertyPath() +
                        ",属性的值" + v.getInvalidValue() +
                        ",校验不通过的提示信息:" + v.getMessage() +
                        ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
        )
                .collect(Collectors.toList());
        return list;
    }
}

3.10 非bean入参校验

package com.zs.validation;

import jakarta.validation.ConstraintViolation;
import jakarta.validation.Validation;
import jakarta.validation.Validator;
import jakarta.validation.executable.ExecutableValidator;
import org.hibernate.validator.HibernateValidator;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class ValidationUtil {

    //线程安全的 http://www.360doc.com/content/16/0222/17/16926569_536478632.shtml
    private static Validator validator;
    private static Validator failFastValidator;
    private static ExecutableValidator executableValidator;

    static {
        //默认校验器
        validator = Validation.buildDefaultValidatorFactory().getValidator();

        //快速失败的校验器
        failFastValidator = Validation.byProvider(HibernateValidator.class)
                .configure().failFast(true) //配置快速失败
                .buildValidatorFactory().getValidator();

        //校验入参或返回值
        executableValidator = validator.forExecutables();
    }

    public static List<String>  valid(Object obj,Class<?>... groups) {
        //如果被校验对象 没有校验通过,则set里面就有校验信息
        Set<ConstraintViolation<Object>> set = validator.validate(obj,groups);
        List<String> list = set.stream().map(v ->
                        "属性:" + v.getPropertyPath() +
                        ",属性的值" + v.getInvalidValue() +
                        ",校验不通过的提示信息:" + v.getMessage() +
                        ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
        )
                .collect(Collectors.toList());
        return list;
    }


    public static List<String>  validFailFast(Object obj,Class<?>... groups) {
        //如果被校验对象 没有校验通过,则set里面就有校验信息
        Set<ConstraintViolation<Object>> set = failFastValidator.validate(obj,groups);
        List<String> list = set.stream().map(v ->
                "属性:" + v.getPropertyPath() +
                        ",属性的值" + v.getInvalidValue() +
                        ",校验不通过的提示信息:" + v.getMessage() +
                        ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
        )
                .collect(Collectors.toList());
        return list;
    }


    /**
     * 非bean参数校验
     *
     * @param object          对象
     * @param method          方法
     * @param parameterValues 参数值
     * @param groups          组
     * @return {@link List}<{@link String}>
     */
    public static <T> List<String> validNotBean(T object, Method method, Object[] parameterValues, Class<?>... groups) {
        Set<ConstraintViolation<Object>> set = executableValidator.validateParameters(object,method,parameterValues,groups);
        
        List<String> list = set.stream().map(v ->
                "属性:" + v.getPropertyPath() +
                        ",属性的值" + v.getInvalidValue() +
                        ",校验不通过的提示信息:" + v.getMessage() +
                        ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
        )
                .collect(Collectors.toList());
        return list;
    }
}
/**
 * 方法非bean类型的入参校验
 * 1. 方法参数前加注释
 * 2. 执行入参校验,真正要用的话可以使用AOP编程来使用,web环境是spring已经做了
 *
 * @param name 名字
 * @return {@link String}
 */
public String getByName(@NotNull String name) {

    StackTraceElement st = Thread.currentThread().getStackTrace()[1];
    String methodName = st.getMethodName();
    Method method = null;
    try {
        method = this.getClass().getDeclaredMethod(methodName,String.class);
    } catch (Exception e) {
        e.printStackTrace();
    }
    List<String> strings = ValidationUtil.validNotBean(this, method, new Object[]{name});
    System.out.println(strings);
    return "ok";
}
@Test
public void test02(){
    UserInfoService userInfoService = new UserInfoService();
    String byName = userInfoService.getByName(null);
    System.out.println(byName);
}

四、web环境中使用

4.1 搭建springboot环境

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-dependencies</artifactId>
      <version>2.5.0</version>
      <scope>import</scope>
      <type>pom</type>
    </dependency>
  </dependencies>
</dependencyManagement>

<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>
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserInfoHandler {

    @GetMapping("/getByName")
    public String getByName(String name){
        return name + "ok";
    }
}
package com.zs;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }
}

4.2 使用@Valid自动校验

/**
 * 编程式校验
 *
 * @param userInfo 用户信息
 * @return {@link String}
 */
@GetMapping("/addUser")
public String addUser(UserInfo userInfo){
    List<String> result = ValidationUtil.valid(userInfo);
    if (result.size() > 0) {
        return "failed";
    } else {
        return "success";
    }
}
    @GetMapping("/addUser2")
    public String addUser2(@Valid UserInfo userInfo, BindingResult bindingResult){
        if (bindingResult.hasErrors()) { //判断是不是满足约束
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println(error.getObjectName() + "::" + error.getDefaultMessage());
            }

            //获取没通过校验的字段详情
            List<FieldError> fieldErrors = bindingResult.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage()
                 + ",当前没通过校验规则的值是:" + fieldError.getRejectedValue());
            }
        }
        return "ok";
    }

userInfo::你的名字不能为空
userInfo::不能为null
userInfo::不能为空
userInfo::不能为null
name:你的名字不能为空,当前没通过校验规则的值是:null
birthday:不能为null,当前没通过校验规则的值是:null
email:不能为空,当前没通过校验规则的值是:null
grade:不能为null,当前没通过校验规则的值是:null

4.3 使用@Validated自动校验 (指定分组)

/**
 * 编程式校验
 *
 * @param userInfo 用户信息
 * @return {@link String}
 */
@GetMapping("/addUser3")
public String addUser3(@Validated(value={UserInfo.Add.class, Default.class}) UserInfo userInfo, BindingResult bindingResult){
    if (bindingResult.hasErrors()) { //判断是不是满足约束
        List<ObjectError> allErrors = bindingResult.getAllErrors();
        for (ObjectError error : allErrors) {
            System.out.println(error.getObjectName() + "::" + error.getDefaultMessage());
        }

        //获取没通过校验的字段详情
        List<FieldError> fieldErrors = bindingResult.getFieldErrors();
        for (FieldError fieldError : fieldErrors) {
            System.out.println(fieldError.getField() + ":" + fieldError.getDefaultMessage()
                    + ",当前没通过校验规则的值是:" + fieldError.getRejectedValue());
        }
    }
    return "ok";
}

4.4 统一异常处理

@GetMapping("/addUser4")
public String addUser4(@Validated(value={UserInfo.Add.class, Default.class}) UserInfo userInfo){
    return "ok";
}

@ExceptionHandler(BindException.class)
public String handleEx(BindException e) {
  List<FieldError> fieldErrors = e.getFieldErrors();
  StringBuilder stringBuilder = new StringBuilder();
  for (FieldError fe : fieldErrors) {
    stringBuilder.append("属性:").append(fe.getField())
      .append("校验不通过,原因:").append(fe.getDefaultMessage())
      .append(";");
  }
  return stringBuilder.toString();
}

4.5 区别@Validated 和 @Valid

  • @Validated 可以指定分组

  • @Validated 支持方法参数的自动校验

@RestController
@Validated //表示整个类都启用校验,如果碰到入参含有bean validation 注解的话,就会自动校验
public class UserInfoHandler {

    @GetMapping("/getByName")
    public String getByName(@NotNull String name){
        return name + "ok";
    }
//@Validated 注解写在方法上的时候报的错误
@ExceptionHandler(BindException.class)
@ResponseBody
public String handleEx(BindException e) {
    List<FieldError> fieldErrors = e.getFieldErrors();
    StringBuilder stringBuilder = new StringBuilder();
    for (FieldError fe : fieldErrors) {
        stringBuilder.append("属性:").append(fe.getField())
                .append("校验不通过,原因:").append(fe.getDefaultMessage())
                .append(";");
    }
    return stringBuilder.toString();
}
//@Validated 注解写在类上报的异常
@ExceptionHandler(ConstraintViolationException.class)
@ResponseBody
public  List<String> handleEx(ConstraintViolationException e) {
    Set<ConstraintViolation<?>> set = e.getConstraintViolations();
    List<String> list = set.stream().map(v ->
                    "属性:" + v.getPropertyPath() +
                            ",属性的值" + v.getInvalidValue() +
                            ",校验不通过的提示信息:" + v.getMessage() +
                            ",消息模板(为被替换的提示信息):" + v.getMessageTemplate()
    )
            .collect(Collectors.toList());
    return list;
}

示例一

一、背景

在某些项目场景中,需要使用代码校验实体类的参数值是否符合需求,并且返回值是动态的情况下,此时需要校验工具类来实现此功能。

二、hibernate-validator

1.maven

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

2.校验工具类

package com.asyf.demo.other_api.hibernatevalidator;

import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.validator.HibernateValidator;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.groups.Default;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @Description 实体校验工具类
 */
@Slf4j
public class ValidateUtil {

    /**
     * 验证器
     */
    private static Validator validator;

    static {
//        validator = Validation.buildDefaultValidatorFactory().getValidator();
        ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class)
                .configure()
                // 快速失败模式
                .failFast(false)
                .buildValidatorFactory();
        validator = validatorFactory.getValidator();
    }


    /**
     * 校验实体,返回实体所有属性的校验结果
     *
     * @param obj
     * @param <T>
     * @return
     */
    public static <T> ValidationResult validateEntity(T obj) {
        //解析校验结果
        Set<ConstraintViolation<T>> validateSet = validator.validate(obj, Default.class);
        return buildValidationResult(validateSet);
    }

    /**
     * 校验指定实体的指定属性是否存在异常
     *
     * @param obj
     * @param propertyName
     * @param <T>
     * @return
     */
    public static <T> ValidationResult validateProperty(T obj, String propertyName) {
        Set<ConstraintViolation<T>> validateSet = validator.validateProperty(obj, propertyName, Default.class);
        return buildValidationResult(validateSet);
    }

    /**
     * 将异常结果封装返回
     *
     * @param validateSet
     * @param <T>
     * @return
     */
    private static <T> ValidationResult buildValidationResult(Set<ConstraintViolation<T>> validateSet) {
        ValidationResult validationResult = new ValidationResult();
        if (!validateSet.isEmpty()) {
            validationResult.setHasErrors(true);
            Map<String, String> errorMsgMap = new HashMap<>();
            for (ConstraintViolation<T> constraintViolation : validateSet) {
                errorMsgMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
            }
            validationResult.setErrorMsg(errorMsgMap);
        }
        return validationResult;
    }

    public static void main(String[] args) {
        User user = new User();
        ValidationResult validationResult = ValidateUtil.validateEntity(user);
        log.info(JSONUtil.toJsonStr(validationResult));
        //og.info(validationResult.getMessage());
    }

}
package com.asyf.demo.other_api.hibernatevalidator;

import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.text.MessageFormat;
import java.util.Map;

@Data
public class ValidationResult {
    /**
     * 是否有异常
     */
    private boolean hasErrors;

    /**
     * 异常消息记录
     */
    private Map<String, String> errorMsg;

    /**
     * 获取异常消息组装
     *
     * @return
     */
    public String getMessage() {
        if (errorMsg == null || errorMsg.isEmpty()) {
            return StringUtils.EMPTY;
        }
        StringBuilder message = new StringBuilder();
        errorMsg.forEach((key, value) -> {
            message.append(MessageFormat.format("{0}:{1} \r\n", key, value));
        });
        return message.toString();
    }

}

3.自定义校验注解

package com.asyf.demo.other_api.hibernatevalidator;

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

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyValidatorAnnotation {

    String message() default "性别有误";

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

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

    String type() default "abc";

}
package com.asyf.demo.other_api.hibernatevalidator;

import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class MyConstraintValidator implements ConstraintValidator<MyValidatorAnnotation, String> {

    private MyValidatorAnnotation annotation;

    public MyConstraintValidator() {
        //每添加一次注解会实例化一个对象
        //log.info("构造函数");
    }

    @Override
    public void initialize(MyValidatorAnnotation annotation) {
        //log.info("初始化");
        this.annotation = annotation;
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        log.info("校验 type:{}", annotation.type());
        //禁用默认的message的值
        context.disableDefaultConstraintViolation();
        //重新添加错误提示语句
        context.buildConstraintViolationWithTemplate("重新添加错误提示语句").addConstraintViolation();
        //返回校验结果
        return "正确的值".equals(value);
    }

}

4.校验实体类

package com.asyf.demo.other_api.hibernatevalidator;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;

import javax.validation.constraints.NotNull;

@Data
public class User {

    //    @NotBlank
    @MyValidatorAnnotation(type = "1", message = "name错误")
    private String name;

    @NotNull
    @JsonIgnore
    private Integer age;

    @MyValidatorAnnotation(type = "2", message = "email错误")
    private String email;

}

5.main函数测试

public static void main(String[] args) {
        User user = new User();
        ValidationResult validationResult = ValidateUtil.validateEntity(user);
        log.info(JSONUtil.toJsonStr(validationResult));
        //og.info(validationResult.getMessage());
    }

三、测试结果

 INFO [main] - 校验 type:2
 INFO [main] - 校验 type:1
 INFO [main] - {"hasErrors":true,"errorMsg":{"name":"重新添加错误提示语句","email":"重新添

https://www.bilibili.com/video/BV1UE411t7BZ?spm_id_from=333.337.search-card.all.click&vd_source=746baba3d7924e4ac64a8318afa1567f

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悠闲的线程池

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值