spring-boot-starter-validation简谈

李昱
2023-12-01

spring-boot-starter-validation

前言:

validation让我们简化了开发过程,可以使用简单的一个注解就实现了很多常见的检验数据的功能,同时支持自定义注解。

  1. 引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  1. 作用:检验数据

  2. 基本注解:

空检查
@Null			验证对象是否为null
@NotNull		验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank		检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty		检查约束元素是否为NULL或者是EMPTY. 

Booelan检查
@AssertTrue		验证 Boolean 对象是否为 true  
@AssertFalse	验证 Boolean 对象是否为 false  

长度检查
@Size(min=, max=)		验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=)		验证注解的元素值长度在min和max区间内

日期检查
@Past		验证 Date 和 Calendar 对象是否在当前时间之前  
@Future		验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern	验证 String 对象是否符合正则表达式的规则

数值检查,建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@Min			验证 Number 和 String 对象是否大等于指定的值  
@Max			验证 Number 和 String 对象是否小等于指定的值  
@DecimalMax		被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin		被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits			验证 Number 和 String 的构成是否合法  
@Digits(integer=,fraction=)		验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。

@Range(min=, max=)	验证注解的元素值在最小值和最大值之间
@Range(min=10000,max=50000,message="range.bean.wage")

@Valid 写在方法参数前,递归的对该对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email  验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

比如,原本验证是否为空我们是这么做:

if(ObjectUtils.isEmpty(mobile)||ObjectUtils.isEmpty(password)){
    return RespBean.error(RespBeanEnum.LOGIN_ERROR);
}
if(!MobileUtil.checkChinaMobile(mobile)){
    return RespBean.error(RespBeanEnum.MOBILE_ERROR);
}

现在只需要在方法参数里为需要验证的参数加@Valid,然后在参数类中给每个属性添加@NotNull注解即可。

像这样:

@RequestMapping(value = "/doLogin",method = RequestMethod.POST)
@ResponseBody
public RespBean doLogin(@Valid LoginUser loginUser){
    ...
}
@Data
public class LoginUser {
    @NotNull
    private String mobile;
    @NotNull(message = "密码不能为空")
    @Length(min = 32)
    private String password;
}

大大简化了后续编写代码的步骤。

上面是对整个对象的所有属性进行校验,那么如果我们只想对某个传参比如

String bookId的校验,该怎么写呢?

@Validated
@RestController
public class BookController {
@RequestMapping(value = "/book/info", method = RequestMethod.GET)
public Object getBookInfo(@NotBlank(message = "书籍ID不能为空") String bookId) {
    return "SUCCESS";
	}
}

必须在类上标明@Validated注解才会生效

自定义注解

首先你可以使用Crtl+B进入任意一个注解查看源代码,发现它们都有类似的结构,因此我们可以根据这些共同点,来开发我们自定义注解,下面以开发自定义验证是否是中国手机号来举例说明。

省略了包名和各种导包...
    
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = { IsMobileVaildator.class })
public @interface IsMobile {
    //下面是可选的4个参数,且提前定义了默认值
    //required:是否是必填项,默认是true
    boolean required() default true;
	
    String message() default "手机号格式错误";

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

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

下面是实现验证过程的类,实现该接口ConstraintValidator<T,T>.

第一个T代表你自定义接口的名字,第二个T代表的是该注解支持哪种对象的类型,这里是手机号自然就是String类型。

public class IsMobileVaildator implements ConstraintValidator<IsMobile,String> {
    private boolean required =false;
	/**
	* 初始化方法
	*
	*/
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        //初始为true意思就是你加了此注解就是保证它是个手机号
        required= constraintAnnotation.required();
    }

    /**
     * 在该方法中写具体验证过程
     * @param value object to validate
     * @param context context in which the constraint is evaluated
     *
     * @return 是否是个中国手机号
     */
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if(required){
           return MobileUtil.checkPhone(value);
        }else {
            //否则就是不是必填,2种情况,一种是没填返回true,一种是填了,既然填了就要验证,将验证的结果返回
            if(ObjectUtils.isEmpty(value)){
                return true;
            }else{
                return MobileUtil.checkPhone(value);
            }
        }
    }
}

以上就是实现自定义注解的全部内容了,类似的身份证验证等等基本都是这样子来写。

将异常展现给前端

前言:

当我们使用了注解后发现,如果数据是异常的,异常信息只会在控制台展示,而前端是没有任何有意义的东西展示出来的。如何将异常展示给前端,下面我们就来实现。

想要了解如何处理异常,就要了解发生异常后程序是怎么执行下去的。

@ControllerAdvice Controller增强器,该注解就是给控制器做统一处理、操作,可入参包名/具体类来控制Controller范围。
@ExceptionHandler(value = Exception.class) 该注解作用就是异常处理,使用@ResquetMapping的方法,发生异常都会进入@ExceptionHandler注解的方法,其实我们可以在单个Controller类中写一个方法加上@ExceptionHandler注解处理,但是每个Controller类还是会有冗余,所以搭配@ControllerAdvice注解就达到了全局统一处理的效果。

说人话就是在某个类上加@ControllerAdvice目的就是将所有控制器都交由该类管理

而在方法上加@ExceptionHandler意思是使用@ResquetMapping的方法发生异常都会被此方法处理。

所以这2个一般是绑定使用,起到全局处理效果。

需要你定义一个全局异常类GlobalException这个类的作用就是用于提示各种异常信息。其实也可以不建这个类,真正处理异常的类是带有**@RestControllerAdvice和@ExceptionHandler(Exception.class)**注解的类。

为什么要定义一个GlobalException,目的是为了手动抛出异常时可以带respBeanEnum中的信息。其实仔细想想共3种可能的异常:

①抛出异常时你想带一些code和message,需要的时候你要手动写。

②校验失败,系统自动抛出校验失败异常。

③很普通的异常你也不想带什么特殊信息,直接报个系统异常的那种,系统排除不是上述2种类型后会自动以该种类型异常抛出。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class GlobalException extends RuntimeException{
    private RespBeanEnum respBeanEnum;
}

声明:

BindException:校验不通过抛出此异常,系统自动抛出。

GlobalException:校验都通过了,但可能是在你登录功能上密码比对错误你手动抛出的异常(普通异常)。比如:

if (user == null) {
    throw new GlobalException(RespBeanEnum.LOGIN_ERROR);
}
抛出的这个对象就会被@ExceptionHandler标记的方法检测到,instanceof是判断类型是否是GlobalException类型,如果是就将exception转为GlobalException对象,调用该对象自带Get方法(globalException.getRespBeanEnum())就可以拿到RespBeanEnum.LOGIN_ERROR信息,再把它放到RespBean.error()中返回即可。
这也解释了为什么下面方法的返回值是RespBean类型。
//声明为异常处理器
@SuppressWarnings("all")
@RestControllerAdvice
public class GlobalExceptionHandler {
    //进行具体异常分类处理
    //拦截所以异常进行抓取 处理
    @ExceptionHandler(Exception.class)
    public RespBean exceptionHandle(Exception exception) {
        //判断异常类型
        if (exception instanceof GlobalException) {
            //拦截的是自定义的异常
            GlobalException globalException = (GlobalException) exception;
            // 参数为 全局异常的枚举
            return RespBean.error(globalException.getRespBeanEnum());
        } else if (exception instanceof BindException) {
            //拦截的是validator绑定的异常
            BindException bindException = (BindException) exception;
            //定义返回为绑定错误
            RespBean respBean = RespBean.error(RespBeanEnum.BIND_ERROR);
            //设置数据为绑定消息
            respBean.setMessage(
                    RespBeanEnum.BIND_ERROR.getMessage()
                            + ":::" + bindException.getBindingResult()
                            .getAllErrors()
                            .get(0)
                            .getDefaultMessage()
            );
            return respBean;
        }
        //不属于那两个异常 就返回系统异常错误
        return RespBean.error(RespBeanEnum.ERROR);
    }
}

bindException.getBindingResult().getAllErrors().get(0).getDefaultMessage()是获取校验异常message的固定写法,不必纠结,想了解可以打断点。

以上就是validation的全部重点内容了,不足之处肯定是有的,希望对你们有帮助。

 类似资料: