Hibernate Validator 参数校验

验证判断

  • Null
  • Empty
  • Blank

"“不为Null,Empty可以是” "(中间有空格),不为Blank就一定要带上字符

在service层做参数验证可以减少代码的冗余,如果这个业务方法被多个控制层调用,就不需要在每一个控制层进行一次参数验证。

传统的参数验证

自己写逻辑代码实现

/**
 * @Description: 验证参数:
 *                      id必须为null
 *                      pid不能为null
 *                      name不能为空,长度必须大于0
 *                      creatTime 不能为未来时间
 * @Param clazz:
 * @return java.lang.String
 * @date 2021/8/21 12:05
*/
@PostMapping("/add")
public String add(@RequestBody Clazz clazz){
    if (clazz.getId() != null){
        return "实体参数异常";
    }
    if (clazz.getPid() < 0 || clazz.getPid() == null){
        return "父班级异常";
    }
    if (clazz.getName() == null || "".equals(clazz.getName())){
        return "班级名称不能为空";
    }
    if (clazz.getCreateTime() == null){
        clazz.setCreateTime(LocalDateTime.now());
    }
    if (clazz.getCreateTime().isAfter(LocalDateTime.now())){
        return "创建时间不能是未来时间";
    }
    return "ok";
}

使用Validator自动验证

实体类注解修饰,这些注解也可以加在方法参数上

@Data
public class Clazz {

    @Null(message = "id要为null")
    private Integer id;

    @NotNull(message = "父班级不能为空")
    private Integer pid;

    /**
     * @Description:String一定要带字符,不允许只带空字符,字符串一般进行NotBlank判断
     * @date 2021/8/21 13:39
    */
    @NotBlank(message = "班级名称不能为空")
    private String name;

    @PastOrPresent
    @NotNull
    private LocalDateTime createTime;

}

@Validated代表开启注解,@Valid代表验证该参数,一定要开启的

@RestController
@RequestMapping("/clazz")
@Validated
public class ClazzController {

    /**
     * @Description: 验证参数:
     *                      id必须为null
     *                      pid不能为null
     *                      name不能为空,长度必须大于0
     *                      creatTime 不能为未来时间
     * @Param clazz:
     * @return java.lang.String
     * @date 2021/8/21 12:05
    */
    @PostMapping("/add")
    public String add(@RequestBody @Valid Clazz clazz){
        return "ok";
    }

}

异常处理

接口

public interface CommonError {
    Integer getErrCode();
    String getErrMsg();
    CommonError setErrMsg(String errMsg);
}

错误枚举

public enum BusinessErrorEnum implements CommonError{

    fail(99999, "服务器内部开了点小差,请重试。。。。。"),
    PARAM_ERROR(4000,"参数错误"),
    ;

    BusinessErrorEnum(Integer errCode, String errMsg) {
        this.errCode = errCode;
        this.errMsg = errMsg;
    }

    private Integer errCode;
    private String errMsg;


    @Override
    public Integer getErrCode() {
        return this.errCode;
    }

    @Override
    public String getErrMsg() {
        return this.errMsg;
    }

    @Override
    public CommonError setErrMsg(String errMsg) {
        this.errMsg = errMsg;
        return this;
    }
}

全局异常处理

捕获抛出的MethodArgumentNotValidException,并拿到设置好的错误信息(注解上面设置好的message),组成统一的返回对象进行返回

@RestControllerAdvice(basePackages = "cn.edu.guet.validator.controller")
public class GlobalExceptionHandle {

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResultBean methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e){
        /**
         * @Description:把错误参数和错误信息组成一个key-value存到map中返回给前端
         * @date 2021/8/21 15:02
        */
        Map<String, String> errorMsg = e.getBindingResult().getFieldErrors().stream()
                .collect(Collectors.toMap(FieldError::getField, FieldError::getDefaultMessage));
        return ResultBean.fail(BusinessErrorEnum.PARAM_ERROR, errorMsg);
    }

}

业务层参数验证

我们先去掉Controller层中的@Valid和@Validated

业务接口方法参数如果要验证就加@Valid,如果在实现类加会报异常

public interface ClazzSerivce {
    ResultBean add(@Valid Clazz clazz);
}

业务实现类上加@Validated

@Service
@Validated
public class ClazzServiceImpl implements ClazzSerivce{

    @Override
    public ResultBean add(Clazz clazz) {
        return ResultBean.success();
    }
}
  • 全局异常处理
@ExceptionHandler(value = ConstraintViolationException.class)
public ResultBean ConstraintDeclarationExceptionHandle(ConstraintViolationException e){
    Map<Path, String> errorMsg = e.getConstraintViolations().stream()
            .collect(Collectors.toMap(ConstraintViolation::getPropertyPath, ConstraintViolation::getMessage));
    return ResultBean.fail(BusinessErrorEnum.PARAM_ERROR, errorMsg);
}

如果在业务层做参数验证要重新捕获异常,因为这次抛出的是ConstraintViolationException


分组验证

分组验证意思就是一个bean中id属性,在增加的时候我们要求它为空,修改的时候我们要求它不为空,这就矛盾了,所以我们要对@NotNull和@Null进行分组,让框架去能辨认出它们在哪个场景下可以启用

我测试分组验证如果在service层进行验证,用注解自动验证不管用,我们编写一个工具类,就不用注解了,通过工具类调用方法去验证。

先封装一个校验信息对象,方便存储校验信息

package cn.edu.guet.gytmaster.validator;

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

import java.util.HashMap;
import java.util.Map;

/**
 * @Description: 校验后信息封装对象
 * @date 2021/8/22 10:57
*/
@Data
public class ValidatorResult {
    /**
     * @Description:校验后设置是否有错误,状态位
     * @date 2021/8/22 10:58
    */
    private boolean hasErrors = false;

    /**
     * @Description:存放错误信息的
     * @date 2021/8/22 10:58
    */
    private Map<String,String> errorMsgMap = new HashMap<>();

    /**
     * @Description:让业务方法用该方法判断校验是否通过
     * @return boolean
     * @date 2021/8/22 10:58
    */
    public boolean isHasErrors(){
        return hasErrors;
    }

    /**
     * @Description:实现通用的通过格式化字符串信息获取错误结果的msg方法
     * @return java.lang.String
     * @date 2021/8/22 10:59
    */
    public String getErrMsg(){
       return StringUtils.join(errorMsgMap.values().toArray(),",");
    }
}

编写校验工具类

package cn.edu.guet.gytmaster.validator;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.util.Set;

/**
 * @Description:交给Spring管理,在需要用到的业务实现类中进行注入即可
 * @date 2021/8/22 11:09
*/
@Component
public class ValidatorImpl implements InitializingBean {

    private Validator validator;

    @Override
    public void afterPropertiesSet() throws Exception{
        // 将hibernate validator通过工厂的初始化方式使其初始化
        this.validator = Validation.buildDefaultValidatorFactory().getValidator();
    }


    /**
     * @Description: 自定义校验通用方法,校验到的错误信息封装到统一的Result中
     * @Param bean: 校验的bean
     * @Param group: 分组类别
     * @return cn.edu.guet.gytmaster.validator.ValidatorResult
     * @date 2021/8/22 10:52
    */
    public ValidatorResult validate(Object bean, Class<?>... group){
        ValidatorResult result = new ValidatorResult();
        // 如果校验对象bean没有校验通过,则set里面有校验到的错误信息
        Set<ConstraintViolation<Object>> validateSet = validator.validate(bean,group);
        if(validateSet.size()> 0){
            // 把封装错误信息的对象状态设置为有错误
            result.setHasErrors(true);
            validateSet.forEach(validate -> {
                String errMsg = validate.getMessage();
                String propertyName = validate.getPropertyPath().toString();
                result.getErrorMsgMap().put(propertyName,errMsg);
            });
        }
        return result;
    }

}

先建一个分组类管理分组类别

package cn.edu.guet.validator.pojo;

import javax.validation.groups.Default;

/**
 * @author pangjian
 * @ClassName Group
 * @Description 继承Default是为了有些校验注解是不属于自定义分组的,我们希望采用我们自定义分组时,连带其他默认分组的校验注解也生效
 * @date 2021/8/21 16:01
 */

public class ValidationGroup {

    public interface Add extends Default{};

    public interface Update extends Default{};

}
@Data
public class Clazz {

    @Null(message = "插入时id要为null", groups = ValidationGroup.Add.class)
    @NotNull(message = "修改时id不能为null", groups = ValidationGroup.Update.class)
    private Integer id;

    @NotNull(message = "父班级不能为空")
    private Integer pid;

}
// 对clazz进行校验,我们设定Add分组和默认分组有效,@NotNull注解属于Update分组的则不会有效
@Override
public ResultBean add(Clazz clazz) {
    ValidatorResult validatorResult = validatorUtil.validate(clazz, ValidationGroup.Add.class);
    if (validatorResult.isHasErrors()){
        throw new BusinessException(validatorResult.getErrMsg(), BusinessErrorEnum.PARAM_ERROR);
    }
    return ResultBean.success();
}

测试

id为空满足了add分组,没有校验到错误

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值