JSR303注解字段校验

JSR303是一套JavaBean参数校验的标准,定义了很多常用的校验注解
可以直接将这些注解加在我们JavaBean的属性上面就可以在需要校验的时候进行校验了

依赖

<!-- 属性效验-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

一、JSR303定义的校验类型

空检查
@Null传参不包括该字段,或者传参该字段值为null
@NotNull 字段必传,且字段值不能为null,String可为空字符串,Interger不能为空字符串(表单值为 “” 时,可以转换:Stirng为"",Integer为Null),一般加在Interger类型上
@NotBlank字段必传,且字段值不能为null,去掉前后空格长度大于0(trim()),一般加在字符串上
@NotEmpty字段必传,且字段值不能为null,加在字符串上效果同@NotBlank,也可以加在(Array,Collection,Map)上,判断不能null,一般加在集合、列表、Map上
Booelan检查
@AssertTrueBoolean 成员变量的值只能为 true
@AssertFalseBoolean 成员变量的值只能为 false
长度检查
@Size(min=, max=)校验对象(Array,Collection,Map,String)长度是否在给定的范围之内,一般加在列表、集合、Map上
@Length(min=, max=)校验字符串长度是否在指定范围内,只能加在字符串上
日期检查
@Past验证 Date 和 Calendar 对象是否在当前时间之前
@Future验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern验证 String 对象是否符合正则表达式的规则
数值检查建议使用在Stirng,Integer类型,不建议使用在int类型上,因为表单值为“”时无法转换为int,但可以转换为Stirng为"",Integer为null
@MinNumber 和 String 对象值大于等于指定的值
@MaxNumber 和 String 对象值小于等于指定的值
@DecimalMax被标注的值必须不大于约束中指定的最大值. 这个约束的参数是一个通过BigDecimal定义的最大值的字符串表示.小数存在精度
@DecimalMin被标注的值必须不小于约束中指定的最小值. 这个约束的参数是一个通过BigDecimal定义的最小值的字符串表示.小数存在精度
@Digits验证 Number 和 String 的构成是否合法
@Digits(integer=,fraction=)验证字符串是否是符合指定格式的数字,interger指定整数精度,fraction指定小数精度。
@Range(min=, max=)检查数字是否介于min和max之间.
@Range(min=10000,max=50000,message=“range.bean.wage”)private BigDecimal wage;
@Valid递归的对关联对象进行校验, 如果关联对象是个集合或者数组,那么对其中的元素进行递归校验,如果是一个map,则对其中的值部分进行校验.(是否进行递归验证)
@CreditCardNumber信用卡验证
@Email验证是否是邮件地址,如果为null,不进行验证,算通过验证。
@ScriptAssert(lang= ,script=, alias=)
@URL(protocol=,host=, port=,regexp=, flags=)

场景一:前端传过来的字段如何在后台做效验,最老的方法就是if else显得不是很灵活。如果前端传来100个字段就得写许多多余的代码。
第一个场景就是在后台创建的实体和前端传来的字段做对应映射,加上JSR303注解来做灵活的效验

1:给Bean实体添加校验注解:javax.validation.constraints(大部分注解都在这个包下),并定义自己的message提示如下:

二、在Springboot项目中使用

2.1、编写需要校验的Bean

package com.example.jsr.entity;

import com.example.jsr.Constant;
import lombok.Data;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.*;
import java.io.Serializable;
import java.util.List;

/**
 * @author Deyou Kong
 * @description 品牌实体类
 * @date 2023/2/25 9:16 上午
 */

@Data
public class Brand implements Serializable {

    /**
     * ID
     */
    private Integer id;

    /**
     * 品牌名称
     */
    @NotBlank(message = Constant.NAME_NOT_NULL)
    @Length(min = 2, max = 32, message = "品牌长度为2-32")
    private String name;

    /**
     * 描述
     */
    @NotNull(message = "描述不能为空NotNull")
    private String description;

    /**
     * 排序
     */
    @Min(value = 1, message = "不能小于1")
    @Max(value = 10, message = "不能大于10")
    @NotNull(message = "排序不能为空")
    private Integer sort;

    @NotEmpty(message = "关联应用不能为空")
    @Size(min = 1, message = "品牌最少关联一个应用")
    private List<Integer> appList;

}

2.2、Controller方法中增加校验注解

@RestController
@RequestMapping("/brand")
public class BrandController {

    @Resource
    private BrandService brandService;

    @PostMapping("/save")
    public JsonResult saveBrand(@Validated @RequestBody Brand brand, BindingResult bindingResult) {
        System.out.println("进入controller的save方法");

        if (bindingResult.hasErrors()) {
            //1.出现参数非法情况
            Map<String, String> map = new HashMap<>();
            bindingResult.getFieldErrors().forEach(fieldError -> {
                map.put(fieldError.getField(), fieldError.getDefaultMessage());
            });
            JsonResult jsonResult = JsonResult.fail("参数不正确,请检查");
            jsonResult.setData(map);
            return jsonResult;
        } else {
            //2.参数验证通过, 执行正常逻辑
            brandService.saveBrand(brand);
            return JsonResult.commonSuccess();
        }
    }
}

备注:这里一个@Validated (org.springframework.validation.annotation.Validated;)的参数后必须紧挨着一个BindingResult 参数接收参数效验的结果,否则spring会在校验不通过时直接抛出异常

在这里插入图片描述

2.3、统一异常处理

添加BindingResult参数后,虽然可以使后台在出现异常时,进行处理并返回统一的结果。但是我们会发现,我们写了许多与业务不相关的代码,为了解决这个问题,我们可以通过@ControllerAdvice进行异常的统一处理。

1、编写统一异常处理类

package com.example.jsr.advice;

import com.example.jsr.entity.JsonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

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

/**
 * @author Deyou Kong
 * @description 全局异常捕获处理器
 * @date 2023/2/25 10:57 下午
 */

@RestControllerAdvice
@Slf4j
public class GlobalExceptionControllerAdvice {

    /**
     * 出现参数非法情况,抛出MethodArgumentNotValidException异常,在此捕获处理
     * @param e
     * @return
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public JsonResult handlerMethodArgumentNotValidException(MethodArgumentNotValidException e){
        BindingResult bindingResult = e.getBindingResult();
        Map<String, String> map = new HashMap<>();
        bindingResult.getFieldErrors().forEach(fieldError -> {
            map.put(fieldError.getField(), fieldError.getDefaultMessage());
        });
        JsonResult jsonResult = JsonResult.fail("参数不正确,请检查");
        jsonResult.setData(map);
        return jsonResult;
    }

    /**兜底
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    public JsonResult handlerException(Exception e){
        JsonResult jsonResult = JsonResult.fail("未知的系统异常");
        jsonResult.setData(e.getMessage());
        return jsonResult;
    }
}

2、把之前加的BindingResult去掉,还原成原先最干净的代码

@RestController
@RequestMapping("/brand")
public class BrandController {

    @Resource
    private BrandService brandService;

    @PostMapping("/save")
    public JsonResult saveBrand(@Validated @RequestBody Brand brand) {
        System.out.println("进入controller的save方法");
        brandService.saveBrand(brand);
        return JsonResult.commonSuccess();
    }
}

三、分组效验

在简单的数据验证中,我们使用完成了数据验证。但是还存在一些问题,如在添加品牌的时候Id为null,但在修改品牌的时候Id不能为null,这样的话,就冲突了。

那怎么办呢?我们可以给他们分个组,添加操作使用一组验证规则,修改操作使用一组验证规则。这就是分组验证的功能。

以@NotNull注解为例

@Constraint(validatedBy = { })
public @interface NotNull {

	String message() default "{javax.validation.constraints.NotNull.message}";
	
	//分组验证时使用
	Class<?>[] groups() default { };
	...

我们通过@NotNul注解的groups指定属于哪个组

实现步骤:

1、创建AddGroupUpdateGroup接口分别表示添加组和更新组

//这俩个接口只是用来标记的,不需要实现
public interface AddGroup {
}

public interface UpdateGroup {
}

2、实体类中使用注解时,标明该验证规则属于哪个组


@Data
public class Brand implements Serializable {

    /**
     * ID
     */
    @NotNull(message = "ID不能为空", groups = {UpdateGroup.class})
    private Integer id;

    /**
     * 品牌名称
     */
    @NotBlank(message = Constant.NAME_NOT_NULL, groups = {AddGroup.class, UpdateGroup.class})
    @Length(min = 2, max = 32, message = "品牌长度为2-32", groups = {AddGroup.class, UpdateGroup.class})
    private String name;

    /**
     * 描述
     */
    @NotNull(message = "描述不能为空NotNull")
    private String description;

    /**
     * 排序
     */
    @Min(value = 1, message = "不能小于1")
    @Max(value = 10, message = "不能大于10")
    @NotNull(message = "排序不能为空")
    private Integer sort;

    @NotEmpty(message = "关联应用不能为空")
    @Size(min = 1, message = "品牌最少关联一个应用")
    private List<Integer> appList;
}

3、Controller中娇艳注解必须为@Validated

注意:
使用分组功能时,必须使用 @Validated 替代 @Valid,它支持分组效验功能

@PostMapping("/save")
public JsonResult saveBrand(@Validated(AddGroup.class) @RequestBody Brand brand) {
    System.out.println("进入controller的save方法");
    brandService.saveBrand(brand);
    return JsonResult.commonSuccess();
}

@PostMapping("/update")
public JsonResult updateBrand(@Validated(UpdateGroup.class) @RequestBody Brand brand){
    System.out.println("进入controller的update方法");
    brandService.updateBrand(brand);
    return JsonResult.success("");
}

4、测试

1、上面实体类中 name 字段的@NotBlank、@Length两个注解对AddGroup、UpdateGroup两个分组都生效,所以测试结果中都有错误提示
2、ID字段只针对UpdateGroup分组生效,测试结果显示只针对update接口进行判断
3、其他字段的注解均未指定分组,那么在Controller指定分组的情况下,这些字段上面的校验注解不生效
在这里插入图片描述

四、自定义效验注解

步骤:
1、编写一个自定义的效验注解
2、编写一个自定义的效验器
3、关联自定义的效验器和自定义的效验注解

1、Brand实体类中增加一个字段

private String logo;   // 需要判断logo地址是否以http开头

2、、自定义 IsUrl 注解类

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = {IsUrlValidator.class })
public @interface IsUrl {

    //JSR303规范中,要求必须有message、groups、payload这三个方法
    //default: 当message为null时,默认会到ValidationMessages.properties配置文件中找com.fcp.common.valid.ListValue.message的值
    String message() default "{com.fcp.common.valid.ListValue.message}";

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

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

    // 注解中用户配置的字段值,如我指定url的规则存放哪个字段,这里存放在value字段中
    String value() default "";
}

3、编写一个自定义的效验器IsUrlValidator.class
校验器的类名与注解类中Constraint中类名一致


//IsUrl:自定义的注解
//String:注解参数类型
public class IsUrlValidator implements ConstraintValidator<IsUrl, String> {

    /**
     * 接收我们自定义的属性value,默认为""
     */
    private String value = "";

    /**
     * 1、初始化方法:通过该方法我们可以拿到我们的注解
     * @param constraintAnnotation
     */
    @Override
    public void initialize(IsUrl constraintAnnotation) {
        // 接收我们自定义的属性value,默认为""
        value = constraintAnnotation.value();
    }

    /**
     * //2、逻辑处理
     * @param s    前端传参的值
     * @param constraintValidatorContext
     * @return
     */
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        System.out.println("注解获取到的值为:" + value);
        System.out.println("用户传参:"+ s);
        boolean b = s.startsWith(value);
        System.out.println(b);
        return b;
    }
}

4、在实体类中使用注解

    @IsUrl(value = "https:", message = "URL地址不正确", groups = {AddGroup.class, UpdateGroup.class})
    @NotBlank(message = "logo不能为空")
    private String logo;

5、测试
在这里插入图片描述

五、校验顺序

在测试过程中,发现多个注解校验顺序不定,这里还不知道怎么解决
看到一篇文章https://blog.csdn.net/qq_41762594/article/details/109326971,等后面有空在研究

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值