美文网首页
spring validation的使用与自定义校验注解

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

作者: 缓慢移动的蜗牛 | 来源:发表于2019-07-26 14:05 被阅读0次

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 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.1.6.RELEASE</version>
</dependency>
<!--因为lombok ,让代码更简洁-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.8</version>
</dependency>

1.4 实体类

@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;
}

1.5 控制层

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

    return user;
}

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

2.1 添加 hibernate-validate依赖

<!--
如果引入了spring-boot-starter-web就可以不用引用下面的依赖了
-->
<dependency>
    <groupId>org.hibernate</groupId>
   <artifactId>hibernate-validator</artifactId>
    <version>6.0.18.Final</version>
</dependency>

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

import org.apache.commons.lang3.StringUtils;

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

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();
    }

    public boolean hasErrors() {
        return hasErrors;
    }

    public boolean isHasErrors() {
        return hasErrors;
    }

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

    public Map<String, String> getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(Map<String, String> 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 <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 (CollectionUtils.isNotEmpty(validateSet)) {
            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;
    }
}

2.4 使用示例

@PostMapping("/add2")
public R<Boolean> 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<? extends Payload>[] payload() default { };
}

3.2 定义校验类

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

    /**
     * 可以注入任何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的使用与自定义校验注解

      本文链接:https://www.haomeiwen.com/subject/glnrrctx.html