验证判断
- 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分组,没有校验到错误