JSR303数据校验

本文围绕Spring Validation展开,介绍了单一校验处理、统一校验异常处理、默认异常处理等内容。详细阐述了分组校验的使用场景和步骤,以及分组校验的默认校验规则。还讲解了自定义校验注解的实现,包括注解属性、校验器编写、关联注解和校验器及使用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、单一校验处理

如果无法使用,则引用依赖

        <dependency>
			<groupId>javax.validation</groupId>
			<artifactId>validation-api</artifactId>
			<version>1.1.0.Final</version>
		</dependency>
		<dependency>
			<groupId>org.hibernate</groupId>
			<artifactId>hibernate-validator</artifactId>
			<version>5.4.1.Final</version>
		</dependency>
 

 

1)、给Bean添加校验注解
并定义自己的message提示
2)、开启校验功能@valid
效果:校验错误以后会有默认的响应
3)、给校验的bean后紧跟一个BindingResult result,就可以获取校验的结果

代码示例:

@RequestMapping("/save")
    public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
        if(result.hasErrors()){
            Map<String,String> map=new HashMap<>();
            result.getFieldErrors().forEach((item)->{
                //fileError获取到错误提示
                    String message=item.getDefaultMessage();
                //获取错误的属性的名字
                String field=item.getField();
                map.put(field,message);
            });
            return R.error(400,"提交的数据不合法").put("data",map);
        }else{
            brandService.save(brand);
        }
        return R.ok();
    }

 

 

 

二、统一校验异常处理

不使用BindResult 就会抛出异常,统一被拦截

拦截处理:

创建一个异常处理类

 

代码示例:

/**
 * 集中处理所以异常
 */
//路径下的数据校验议场全部都到此处处理
//统一异常处理

 //@ResponseBody
 //@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
 //注:RestControllerAdvice=@ResponseBody+@ControllerAdvice
 @RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {

    //处理什么异常,先找到要处理的异常是哪个
    @ExceptionHandler(value= MethodArgumentNotValidException.class)
    public R handleVaildException(MethodArgumentNotValidException e){
        BindingResult bindingResult=e.getBindingResult();
        Map<String,String> errorMap=new HashMap<>();
        bindingResult.getFieldErrors().forEach((fieldError -> {
            errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
        }));
        return R.error(400,"数据校验出现问题").put("data",errorMap);
    }

}

 

 

三、默认异常处理

代码示例:

@ExceptionHandler(value = Throwable.class)//异常的范围更大
public R handleException(Throwable throwable){
    log.error("未知异常{},异常类型{}",
              throwable.getMessage(),
              throwable.getClass());
    return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
                   BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}

错误状态码:

上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义

上面的用法主要是通过@Controller+@ExceptionHandler来进行异常拦截处理

BizCodeEnum

为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码

package com.atguigu.common.exception;

/***
 * 错误码和错误信息定义类
 * 1. 错误码定义规则为5为数字
 * 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
 * 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
 * 错误码列表:
 *  10: 通用
 *      001:参数格式校验
 *  11: 商品
 *  12: 订单
 *  13: 购物车
 *  14: 物流
 */
public enum BizCodeEnum {

    UNKNOW_EXEPTION(10000,"系统未知异常"),

    VALID_EXCEPTION( 10001,"参数格式校验失败");

    private int code;
    private String msg;

    BizCodeEnum(int code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}

 

四、分组校验

如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。

步骤:

创建分组接口Insert.class Update.class
在VO的属性中标注@NotBlank等注解,并指定要使用的分组,如@NotNull(message = "用户姓名不能为空",groups = {Insert.class,Update.class})
controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)


 

业务方法参数上使用@Validated注解

@Validated的value值指定要使用的一个或多个分组

wu

 

五、分组校验的默认校验

validated

这里要是指定了分组,实体类上的注解就是指定了分组的注解才生效,

没有指定分组的默认不生效,要是没有指定分组,就是对没有指定分组的注解生效,指定分组的注解就不生效了,只能在valid生效

可以在自定义的异常分组接口中继承Default类。所有没有写明group的都属于Default分组。

例:

   默认没有指定分组的校验注解@NotBlank.在分组校验情况groups={AddGroup.class})下不生效

 

此外还可以在实体类上标注@GroupSequece({A.class,B.class})指定校验顺序

通过@GroupSequence指定验证顺序:先验证A分组,如果有错误立即返回而不会验证B分组,接着如果A分组验证通过了,那么才去验证B分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。

关于Default,此处我springvalidation默认生成的验证接口,验证的范围是所有带有验证信息的属性,

若是属性上方写了验证组,则是验证该组内的属性

若是验证实体类类上写了GroupSequence({}) 则说明重写了Default验证接口,Default就按照GroupSequence里所写的组信息进行验证

 

六、自定义校验注解

Hibernate Validator提供了一系列内置的校验注解,可以满足大部分的校验需求。但是,仍然有一部分校验需要特殊定制,例如某个字段的校验,我们提供两种校验强度,当为normal强度时我们除了<>号之外,都允许出现。当为strong强度时,我们只允许出现常用汉字,数字,字母。内置的注解对此则无能为力,我们试着通过自定义校验来解决这个问题。

场景:要校验showStatus的0/1状态,可以用正则,但我们可以利用其他方式解决复杂场景。比如我们想要下面的场景
 

/**
	 * 显示状态[0-不显示;1-显示]
	 */
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class})
private Integer showStatus;

 

<!--校验-->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.1.0.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>5.4.1.Final</version>
</dependency>
<!--高版本需要javax.el-->
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.1-b08</version>
</dependency>

1、自定义校验注解

必须有3个属性

  • message()错误信息
  • groups()分组校验
  • payload()自定义负载信息

// 自定义注解
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 哪都可以标注
@Retention(RUNTIME)
public @interface ListValue {
    // 使用该属性去Validation.properties中取
    String message() default "{com.atguigu.common.valid.ListValue.message}";

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

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

    // 数组,需要用户自己指定
    int[] value() default {};
}

因为上面的message值对应的最终字符串需要去ValidationMessages.properties中获得,所以我们在common中新建文件ValidationMessages.properties

文件内容

com.atguigu.common.valid.ListValue.message=必须提交指定的值 [0,1]

2、自定义校验器ConstraintValidator

上面只是定义了异常消息,但是怎么验证是否异常还没说,下面的ConstraintValidator就是说的

比如我们要限定某个属性值必须在一个给定的集合里,那么就通过重写initialize()方法,指定可以有哪些元素。

而controller接收到的数据用isValid(验证

public class ListValueConstraintValidator 
    implements ConstraintValidator<ListValue,Integer> { //<注解,校验值类型>
    
    // 存储所有可能的值
    private Set<Integer> set=new HashSet<>();
    
    @Override // 初始化,你可以获取注解上的内容并进行处理
    public void initialize(ListValue constraintAnnotation) {
        // 获取后端写好的限制 // 这个value就是ListValue里的value,我们写的注解是@ListValue(value={0,1})
        int[] value = constraintAnnotation.value();
        for (int i : value) {
            set.add(i);
        }
    }

    @Override // 覆写验证逻辑
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        // 看是否在限制的值里
        return  set.contains(value);
    }
}

具体的校验类需要实现ConstraintValidator接口,第一个泛型参数是所对应的校验注解类型,第二个是校验对象类型。在初始化方法initialize中,我们可以先做一些别的初始化工作,例如这里我们获取到注解上的value并保存下来,然后生成set对象。

真正的验证逻辑由isValid完成,如果传入形参的属性值在这个set里就返回true,否则返回false


 

3、关联校验器和校验注解

@Constraint(validatedBy = { ListValueConstraintValidator.class})

一个校验注解可以匹配多个校验器

 

      4、使用实例

/**
	 * 显示状态[0-不显示;1-显示]
	  用value[]指定可以写的值
	 */
	@ListValue(value = {0,1},groups ={AddGroup.class})
	private Integer showStatus;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值