spring validation的使用与自定义校验注解

1. validate介绍

1.1 前言

前端录入数据,在后台进行校验工作是必不可少的。例如:为空校验,邮箱格式校验,手机号校验等。
后端校验的目的是避免用户绕过浏览器,使用http工具直接向后端请求一些违法数据,服务端的数据校验也是必要的,可以防止脏数据落到数据库中,如果数据库中出现一个非法的邮箱格式,也会让运维人员头疼不已。

下面介绍spring 框架中的自动校验机制。

1.2 框架提供的校验注解

注解 说明
@NotNull 值不能为空
@Null 值必须为空
@Pattern(regexp=) 字符串必须匹配正则表达式
@Size(min=,max=) 集合的元素数量必须在min和max之间
@CreditCardNumber(ignoreNonDigitCharacters) 字符串必须是信用卡号(按美国标准校验)
@Email 字符串必须是Email邮箱
@Length(min=,max=) 检查字符串的长度
@NotBlank 字符串必须有字符
@NotEmpty 字符串不为null,集合有元素
@Range(min=,max=) 数字必须大于等于min,小于等于max
@SafeHtml 字符串是安全的html
@URL 字符串是合法的URL
@AssertFalse 值必须是false
@AssertTrue 值必须是true
@DecimalMax(value=,inclusive=) 值必须小于等于(inclusive=true)/小于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上
@DecimalMin(value=,inclusive=) 值必须大于等于(inclusive=true)/大于(inclusive=false) value属性指定的值,可以注解在字符串类型的属性上
@Digits(integer=,fraction=) 数字格式检查,integer指定整数部分的最大长度,fraction指定小数部分的最大长度
@Future 值必须是未来的日期
@Past 值必须是过去的日期
@Max(value=) 值必须小于等于value指定的值,不能注解在字符串类型的属性上
@Min(value=) 值必须大于等于value指定的值,不能注解在字符串类型的属性上

1.3 引入依赖


    org.springframework.boot
    spring-boot-starter-web
    2.1.6.RELEASE



    org.projectlombok
    lombok
    1.18.8

1.4 实体类(包含嵌套实体的校验)

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Past;
import java.io.Serializable;
import java.util.Date;
import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class User implements Serializable {
    private static final long serialVersionUID = -8441401775719174836L;

    private Integer id;
    private String userName;

    @NotBlank(message = "密码不能为空")
    private String password;

    @Past(message = "生日必须是过去的时间")
    private Date birthDay;

    @NotEmpty(message = "地址信息不能为空")
    @Valid
    private List<@NotNull(message = "地址信息不能为空") Address> addressList;

    @Data
    public static class Address implements Serializable{
        private static final long serialVersionUID = -6795109699856978126L;

        @NotEmpty(message = "手机号不能为空")
        private String mobile;

        private String detail;
    }
}

1.5 控制层

import com.google.common.collect.Maps;
import com.nanc.demo.modules.test.entity.bo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/test")
public class MyTestController {
    /**
     * 说明:
     *      添加 BindingResult参数后,就算验证不通过,也能继续执行方法体
     *      BindingResult的参数的作用:
     *              方便记录日志(如果不想继续向下执行,可以直接再抛异常)
     * @param user
     * @param result
     * @return
     */
    @PostMapping()
    public Object create(@Valid @RequestBody User user, BindingResult result){
        // 判断BindingResult是否保存错误的验证信息,如果有,则直接return
        if (result.hasErrors()) {
            Map errors = Maps.newHashMap();
            result.getFieldErrors().stream().forEach(f -> errors.put(f.getField(), f.getDefaultMessage()));
            return errors;
        }
        //对 user的处理,然后返回
        return user;
    }
}

1.6 请求测试

### 测试--数组中传 null
POST http://127.0.0.1:8888/demo/test/
Content-Type: application/json

{
    "userName": "aaaaa",
    "password": "bbbbb",
    "birthDay": "2022-07-22 11:27:56",
    "addressList": [
        null
    ]
}

### 测试--嵌套实体不传值
POST http://127.0.0.1:8888/demo/test/
Content-Type: application/json

{
    "userName": "aaaaa",
    "password": "bbbbb",
    "birthDay": "2022-07-22 11:27:56",
    "addressList": [
    ]
}

### 测试--手机号不填
POST http://127.0.0.1:8888/demo/test/
Content-Type: application/json

{
    "userName": "aaaaa",
    "password": "bbbbb",
    "birthDay": "2022-07-22 11:27:56",
    "addressList": [
        {
            "detail": "abc"
        }
    ]
}

###

2 Hibernate-validate工具类,手动调用校验返回结果

2.1 添加 hibernate-validate依赖



    org.hibernate
   hibernate-validator
    6.0.18.Final

2.2 接收处理结果,以及输出格式化的一个实体类

import org.apache.commons.lang3.StringUtils;

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

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

    /**
     * 异常消息记录
     */
    private Map 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();
    }

    public boolean hasErrors() {
        return hasErrors;
    }

    public boolean isHasErrors() {
        return hasErrors;
    }

    public void setHasErrors(boolean hasErrors) {
        this.hasErrors = hasErrors;
    }

    public Map getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(Map errorMsg) {
        this.errorMsg = errorMsg;
    }

    @Override
    public String toString() {
        return "ValidationResult{" +
                "hasErrors=" + hasErrors +
                ", errorMsg=" + errorMsg +
                '}';
    }
}

2.3 创建工具类,提供公共方法校验,返回结果

import org.apache.commons.collections.CollectionUtils;

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

public class ValidateUtil {

    private ValidateUtil() {
    }

    /**
     * 验证器
     */
    private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();


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

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

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

2.4 使用示例

@PostMapping("/add2")
public R add(@RequestBody MyTest myTest) throws Exception {
    ValidationResult validationResult = ValidateUtil.validateEntity(myTest);
    if (validationResult.hasErrors()) {
        return R.error(validationResult.getMessage());
    }
    return R.ok(true);
}

3 自定义校验注解

自定义注解用途:校验字段的在数据库里的唯一性等

3.1 定义注解类

**
 * 自定义校验注解
 *  类似与 @NotEmpty
 *
 *  @Constraint(validatedBy = MyConstraintValidator.class)  指明这是一个校验的注解
 *      validatedBy = MyConstraintValidator.class 指定由MyConstraintValidator类去校验
 *
 *  必须定义三个成员  message,groups,payload
 *
 */
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = MyConstraintValidator.class)
public @interface MyConstraint {
    String message() default "{javax.validation.constraints.NotEmpty.message}";
    Class[] groups() default { };
    Class[] payload() default { };
}

3.2 定义校验类

/**
 * 不用添加 @Component注解
 * spring会扫描实现了ConstraintValidator 接口的类后,然后自动管理
 *
 * ConstraintValidator:
 *   泛型参数说明:
 *          第一个参数 指定校验MyConstraint注解的字段
 *          第二参数 注解修改字段的类型
 * @author chennan
 * @date 2019.7.22 22:19
 */
public class MyConstraintValidator implements ConstraintValidator {

    /**
     * 可以注入任何spring管理的服务
     */
    @Autowired
    private HelloService helloService;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        System.out.println("------------MyConstraintValidator---------");
        System.out.println(value);
        System.out.println(helloService.greeting("tom"));
        System.out.println("------------MyConstraintValidator---------");

        //true-校验通过   false-校验不通过
        return false;
    }

    @Override
    public void initialize(MyConstraint constraintAnnotation) {
        System.out.println("my validator init");
    }
}

3.3 使用自定义校验注解

public class User implements Serializable{
    private static final long serialVersionUID = -8441401775719174836L;
    @MyConstraint(message = "测试自定义注解")
    private String userName;
}

你可能感兴趣的:(spring validation的使用与自定义校验注解)