java后端JSR303数据校验

JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是Hibernate Validator。

使用步骤:

1. 基本使用

1)给java bean上需要做校验的属性添加校验注解

例如:

@NotBlank
private String name;

java给我们提供了很多数据校验注解,统一定义在 包 javax.validation.constraints 下

2)在Controller层,给需要校验的地方加上校验注解@Valid,开启校验功能

例如:

@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
    brandService.save(brand);
    return R.ok();
}

在controller处理函数的实体bean上添加注解@Valid校验注解之后,在提交请求的时候,就会对提交的数据进行数据校验

3)使用自定义的校验不通过提示信息

当校验不通过的时候,java定义了默认的校验不通过提示信息,如果我们不想使用默认提示信息,可以添加message属性。例如:

@URL(message = "logo必须是一个合法的URL地址")
private String logo;

4)获取校验结果信息

在开发规范中,我们一般希望系统可以统一响应对象,如果对校验不做处理,那么返回的信息与我们规范不符合,这时候,我们可以使用BindingResult,BindingResult封装了校验结果,定义紧跟在@Valid修饰的实体bean的后面

例如:

@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
    if(result.hasErrors()){
        Map<String,String> map = new HashMap<>();
        result.getFieldErrors().stream().forEach(item -> {
            map.put(item.getField(),item.getDefaultMessage());  // 如果有自定义消息,那么getDefaultMessage获取的是自定义的,否则就是默认的消息
        });
        return R.error(400,"提交的数据不合法").put("data",map);
    }
    brandService.save(brand);
    return R.ok();
}

如果每一个校验都这样处理,会很麻烦,所以,我们可以定义一个统一异常处理器类。

例如:

package com.bjc.gulimall.product.exception;

import com.bjc.common.enums.BizCodeEnume;
import com.bjc.common.utils.R;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;

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

//@ControllerAdvice
//@ResponseBody
@RestControllerAdvice(basePackages="com.bjc.gulimall.product.controller")
@Slf4j
public class GulimallExceptionControllerAdvice {

    //处理JSR303数据校验抛出的异常
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public R handleValidException(MethodArgumentNotValidException e){
        // 打印日志
        log.error("提交的数据不合法",e);
        BindingResult bindingResult = e.getBindingResult();
        Map<String,String> errMap = new HashMap<>();
        bindingResult.getFieldErrors().stream().forEach(item -> {
            errMap.put(item.getField(),item.getDefaultMessage());
        });
        return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errMap);
    }

    @ExceptionHandler(value = Throwable.class)
    public R handleException(Throwable e){
        log.error("出现异常:",e);
        return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
    }

}

这样,在controller中,就不需要处理异常了,可以放心的抛了。

注意:对于某一种验证规则是适用于一种数据类型的,简单说来,正则则表达式的验证对象可以为String类型的,但是不可以为Integer类型的数据,那么当我们使用正则表达式进行验证的时候就会出现错误

2. 分组校验groups

有时候,我们有这样的需求,同一个属性字段,在不同的情况下有不同的校验规则,这时候,我们就需要用到分组校验了。

使用步骤:

1)新建两个用于标注分组的接口(接口为空接口即可)

例如:

添加分组接口:

package com.bjc.common.validation;

public interface AddGroup {
}

更新分组接口:

package com.bjc.common.validation;

public interface UpdateGroup {
}

注意:为什么需要该类型的接口,是因为分组校验的时候,需要传递不同的分组接口

2)在实体类上添加groups分组,并制定分组接口

例如:

2.1 id在新增的时候需要为空,在更新的时候不能为空

@NotNull(message = "更新id不能为空",groups = UpdateGroup.class)
@Null(message = "新增id必须为空",groups = AddGroup.class)
private Long brandId;

2.2 name在新增和更新都不能为空

@NotBlank(message = "品牌名必须不能为空",groups = {UpdateGroup.class, AddGroup.class})
private String name;

3)在controller上添加注解@Validated,并指定分组接口

例如:

public R update(@Validated(value={UpdateGroup.class}) @RequestBody BrandEntity brand){}

注意:

3.1 这里校验注解为@Validated,该注解是spring提供的,是对@Valid的实现

3.2 如果开启了分组校验,那么校验属性字段都需要指定分组,否则没指定分组的属性校验会失效

3.3 如果在controller中校验注解没有指定分组,那么java bean中指定了分组的校验会失效,没有指定生效

3. 自定义校验注解

有时候@Pattern正则匹配不能满足业务需求,这时候就需要使用自定义校验注解了。

3.1 自定义校验注解

可以参看官方提供的校验注解,校验注解必须包含三个属性message、groups、payload。可以参考官方提供的@NotBlank注解,自定义我们自己的注解。例如:

package com.bjc.common.validation.selfvalidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/*
* 自定义校验注解
*
* */
@Documented
@Constraint(validatedBy = {})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListIntegerValue {
    /*
    * 校验注解中,必须要有message  groups  payload 三个属性 可以直接拷贝JDK提供的校验注解中提供的
    * */
    String message() default "{javax.validation.constraints.ListValue.message}";

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

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

    /*
    * 需要指定的值
    * */
    int[] vals() default {};
}

注意:message的默认值,可以参考官方提供的配置文件,新建我们自己的配置文件,写入我们自定义的错误描述信息,例如:

 

怎么查看官方配置文件,直接搜索官方提供的校验注解的message的默认值,例如:搜索 javax.validation.constraints.Min.message即可找到对应的文件。

然后还需要添加校验依赖

<!-- 添加校验api -->
<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>

3.2 自定义校验器

校验器的作用是校验注解标注的字段。从上面的注解中,我们可以看到有一个注解@Constraint,其有一个属性validateBy,该属性接收校验器,进入@Constraint注解,发现其属性validateBy是一个Class数组,泛型为ConstraintValidator,如图:

进入ConstraintValidator该,发现其是一个接口,如图:

所以,我们自定义校验器只需要实现 ConstraintValidator接口即可。例如:

package com.bjc.common.validation.selfvalidator;

import org.apache.commons.lang.ArrayUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * 自定义Integer类型指定数值校验注解校验器
 * */
public class ListValueConstraintValidatorForInteger implements ConstraintValidator<ListValue,Integer> {

    Set<Integer> set = new HashSet<>();

    /*
    * 初始化方法,用于获取自定义注解的详细信息
    * */
    @Override
    public void initialize(ListValue constraintAnnotation) {
        int[] vals = constraintAnnotation.vals();
        if(ArrayUtils.isNotEmpty(vals)){
            Arrays.stream(vals).forEach(set::add);
        }
    }

    /*
    * 校验值是否符合要求
    * */
    @Override
    public boolean isValid(Integer val, ConstraintValidatorContext constraintValidatorContext) {
        return set.contains(val);
    }
}

3.3 使用自定义校验注解

在自定义注解上加上validatedBy的属性值为校验器的class,如图:

然后,在java bean中需要使用到自定义注解的字段上添加上自定义注解,例如:

@ListValue(vals = {0,1},groups = {UpdateStatusGroup.class})
// @ListIntegerValue(vals = {0,1},message = "状态值不是指定的值!")
private Integer showStatus;

完整的自定义注解定义:

package com.bjc.common.validation.selfvalidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/*
* 自定义校验注解
*
* */
@Documented
@Constraint(validatedBy = {ListValueConstraintValidatorForInteger.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ListValue {
    /*
    * 校验注解中,必须要有message  groups  payload 三个属性 可以直接拷贝JDK提供的校验注解中提供的
    * */
    String message() default "{javax.validation.constraints.ListValue.message}";

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

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

    /*
    * 需要指定的值
    * */
    int[] vals() default {};
}

注意:我们可以在validatedBy 中指定多个校验器,适配不同类型的数据校验。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值