欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

JSR303校验的简单使用以及自定义校验规则的代码编写

发布时间:2024/3/13 65 豆豆
生活随笔 收集整理的这篇文章主要介绍了 JSR303校验的简单使用以及自定义校验规则的代码编写 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

文章目录

  • 一、JSR303校验
      • 1、简介
      • 2、相关注解
      • 3、JSR303依赖包
  • 二、JSR303自带的校验规则
      • 1、在JavaBean上添加校验规则
      • 2、生效校验规则
          • 2.1 controller返回的通用实体类R
          • 2.2 方法一:在controller的方法中生效校验
          • 2.3 方法二:写一个异常处理类
      • 2、使用group校验
          • 2.1 分组校验
          • 2.1 @Validated与@Valid
          • 2.2 定义接口,充当标识
          • 2.3 测试
      • 3、其他的校验规则
          • 3.1 组序列
          • 3.2 级联验证
  • 三、自定义校验规则
      • 1、编写自定义注解
      • 2、编写配置文件ValidationMessages.properties
      • 3、测试


环境:
IDEA: 2021
JDK: 1.8
Spring-Boot-dependencies: 2.2.5.RELEASE
mysql: 5.7
mybatis-plus-boot-starter: 3.2.0

一、JSR303校验

1、简介

参数校验是程序开发中必不可少的步骤。用户在前端页面上填写表单时,前端js程序会校验参数的合法性,当数据到了后端,为了防止恶意操作,保持程序的健壮性,后端同样需要对数据进行校验。后端参数校验最简单的做法是直接在业务方法里面进行判断,当判断成功之后再继续往下执行。但这样带给我们的是代码的耦合,冗余。当我们多个地方需要校验时,我们就需要在每一个地方调用校验程序,导致代码很冗余,且不美观。
那么如何优雅的对参数进行校验呢?JSR303就是为了解决这个问题出现的。

2、相关注解

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

注解如下:

注解说明
@NotNull注解元素必须是非空
@NotBlank注解元素不能是空格并且至少包含一个字符
@NotEmpty注解元素不能为null或空
@Email该字符串必须是格式正确的电子邮件地址。
@Null注解元素必须是空
@Digits带注释的元素必须是可接受范围内的数字
@Future带注释的元素必须是未来的瞬间、日期或时间。
@FutureOrPresent注释元素必须是当前或未来的瞬间、日期或时间。
@Past带注释的元素必须是过去的瞬间、日期或时间。
@PastOrPresent带注释的元素必须是过去或现在的瞬间、日期或时间。
@Max带注释的元素必须是一个数字,其值必须小于或等于指定的最大值。
@Min带注释的元素必须是一个数字,其值必须大于或等于指定的最小值。
@Pattern带注释的 {@code CharSequence} 必须匹配指定的正则表达式。正则表达式遵循 Java 正则表达式约定
@Size验证元素大小是否在指定范围内
@DecimalMax带注释的元素必须是一个数字,其值必须小于或等于指定的最大值。
@DecimalMin带注释的元素必须是一个数字,其值必须大于或等于指定的最小值。
@AssertTrue被注释的元素必须为true
@AssertFalse被注释的元素必须为false
@Positive被注解的元素必须是整数
@PositiveOrZero被注解元素必须是正数或0

Hibernate validatorJSR303的基础上对校验注解进行了扩展,hibernate-validator官方文档

扩展注解如下:

例举几个:

注解说明
@URL被注释的元素必须是合法的URL
@Length被注释的字符串的大小必须在指定的范围内
@Range被注释的元素必须在合适的范围内

3、JSR303依赖包

<!--JSR303依赖包--><dependency><groupId>javax.validation</groupId><artifactId>validation-api</artifactId><version>2.0.1.Final</version></dependency><!--hibernate-validator依赖包,包含了JSR303的依赖包--><dependency><groupId>org.hibernate.validator</groupId><artifactId>hibernate-validator</artifactId><version>6.0.17.Final</version></dependency>

二、JSR303自带的校验规则

1、在JavaBean上添加校验规则

package com.zhuang.mall.product.entity;import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName;import java.io.Serializable; import java.util.Date; import lombok.Data; import org.hibernate.validator.constraints.URL;import javax.validation.constraints.*;/*** 品牌* * @author mrzhuang* @email 862627527@qq.com* @date 2022-04-11 13:56:19*/ @Data @TableName("pms_brand") public class BrandEntity implements Serializable {private static final long serialVersionUID = 1L;/*** 品牌id*/@TableIdprivate Long brandId;/*** 品牌名*/@NotBlank(message = "品牌名必须填写!")private String name;/*** 品牌logo地址*/@NotEmpty(message = "logo url必须填写!")@URL(message = "必须是合法的url!")private String logo;/*** 介绍*/private String descript;/*** 显示状态[0-不显示;1-显示]*/private Integer showStatus;/*** 检索首字母*/@NotEmpty(message = "首字母必须填写!")// 正则表达式不能写为"/^[a-zA-Z]$/"@Pattern(regexp = "^[a-zA-Z]$" , message = "检索首字母必须是一个字母!")private String firstLetter;/*** 排序*/@NotNull(message = "排序必须填写!")@Min(value = 0, message = "排序必须是大于等于0的整数!")private Integer sort;}}

2、生效校验规则

2.1 controller返回的通用实体类R
/*** Copyright (c) 2016-2019 人人开源 All rights reserved.** https://www.renren.io** 版权所有,侵权必究!*/package com.zhuang.common.utils;import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.TypeReference; import org.apache.http.HttpStatus;import java.util.HashMap; import java.util.Map;/*** @author mrzhuang* @date 2022/4/25 8:12 PM*/ public class R extends HashMap<String, Object> {private static final long serialVersionUID = 1L;public R() {put("code", HttpStatus.SC_OK);put("msg", "success");}public R setData(Object data){put("data",data);return this;}public <T> T getData(String key,TypeReference<T> typeReference){Object data = get(key);String s = JSON.toJSONString(data);T t = JSON.parseObject(s, typeReference);return t;}public <T> T getData(TypeReference<T> typeReference){Object data = get("data");String s = JSON.toJSONString(data);T t = JSON.parseObject(s, typeReference);return t;}public static R error() {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");}public static R error(String msg) {return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);}public static R error(int code, String msg) {R r = new R();r.put("code", code);r.put("msg", msg);return r;}public static R ok(String msg) {R r = new R();r.put("msg", msg);return r;}public static R ok(Map<String, Object> map) {R r = new R();r.putAll(map);return r;}public static R ok() {return new R();}public R put(String key, Object value) {super.put(key, value);return this;}public Integer getCode(){return (Integer) this.get("code");} }
2.2 方法一:在controller的方法中生效校验
package com.zhuang.mall.product.controller;import java.util.Arrays; import java.util.HashMap; import java.util.Map;import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;import com.zhuang.mall.product.entity.BrandEntity; import com.zhuang.mall.product.service.BrandService; import com.zhuang.common.utils.PageUtils; import com.zhuang.common.utils.R;import javax.naming.Binding; import javax.validation.Valid;/*** 品牌* @author mrzhuang* @email 862627527@qq.com* @date 2022-04-11 13:56:19*/ @RestController @RequestMapping("product/brand") public class BrandController {@Autowiredprivate BrandService brandService;/*** 保存*///@Valid告诉spring mvc上传的数据需要校验,brandEntity中的@NotBlank规则就会生效!//BindingResult会获取到错误的信息结果@RequestMapping("/save")public R save(@RequestBody @Valid BrandEntity brand, BindingResult result){//判断result中是否有错误if (result.hasErrors()) {Map<String, String > map = new HashMap<>();//获取到校验的错误结果result.getFieldErrors().forEach((item)->{//FiledError 获取到错误提示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();}}

测试:
使用Postman进行接口的测试。
错误的输入测试:
1、传入空的jason字符串:

{}

2、传入不合校验规则的Jason字符串:

{"name":"","logo":"123","sort":-1,"firstLetter":"ab"}

正确的输入测试:
传入的jason字符串为:

{"name":"小米","logo":"https://gss0.baidu.com/7LsWdDW5_xN3otqbppnN2DJv/forum/pic/item/fd039245d688d43f394f6821381ed21b0ff43b7b.jpg","sort":0,"firstLetter":"A"}
2.3 方法二:写一个异常处理类

对于第一种方法,代码的冗余度比较高。使用统一的异常处理类降低冗余、方便。

1、创建通用的异常枚举类
mall-common/src/main/java/com/zhuang/common/exception/BizCodeEnum.java

package com.zhuang.common.exception;/*** @author mrzhuang* @date 2022/4/25 5:25 PM */ public enum BizCodeEnum {UNKNOWN_EXCEPTION(10000,"系统未知错误"),VALID_EXCEPTION(10001,"参数校验异常"),private Integer code;private String msg;//枚举类必有的私有构造器private BizCodeEnum(Integer code,String msg){this.code = code;this.msg = msg;}public Integer getCode() {return code;}public String getMsg() {return msg;} }

2、创建异常类
mall-product/src/main/java/com/zhuang/mall/product/exception/MallExceptionControllerAdvice.java

package com.zhuang.gulimall.product.exception;import com.zhuang.common.exception.BizCodeEnum; import com.zhuang.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.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap; import java.util.Map;/*** @author mrzhuang* @date 2022/4/24 8:54 PM* description:集中处理所有的异常*/ @Slf4j @RestControllerAdvice(basePackages = "com.zhuang.gulimall.product.controller") public class MallExceptionControllerAdvice {//指定处理的异常@ExceptionHandler(value = MethodArgumentNotValidException.class)public R handleValidException(MethodArgumentNotValidException e) {log.error("数据校验出现问题{},异常类型: {}",e.getMessage(),e.getClass());BindingResult bindingResult = e.getBindingResult();Map<String, String> errorMap = new HashMap<>();bindingResult.getFieldErrors().forEach((fieldError -> {errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());}));return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",errorMap);}//处理任意类型的异常,将异常抛出@ExceptionHandler(value = Throwable.class)public R handleException(Throwable throwable) {log.error("错误:", throwable);return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(),BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());} }

3、还原初始controller类中的方法
方法中的@Valid注解需要加上!!!

/*** 品牌* @author mrzhuang* @email 862627527@qq.com* @date 2022-04-11 13:56:19*/ @RestController @RequestMapping("product/brand") public class BrandController {@Autowiredprivate BrandService brandService;/*** 保存*///@Valid告诉spring mvc上传的数据需要校验,brandEntity中的@NotBlank规则就会生效!//BindingResult会获取到错误的信息结果@RequestMapping("/save")public R save(@RequestBody @Valid BrandEntity brand){brandService.save(brand);return R.ok();}

4、测试
输入错误的jason字符串:

{"name":"","logo":"123","sort":-1,"firstLetter":"xx"}

输入正确的jason字符串:

{"name":"小米","logo":"https://gss0.baidu.com/7LsWdDW5_xN3otqbppnN2DJv/forum/pic/item/fd039245d688d43f394f6821381ed21b0ff43b7b.jpg","sort":0,"showStatus":"0","firstLetter":"x"}

2、使用group校验

2.1 分组校验

分组校验可以将JavaBean的某个属性值进行分组,比如在进行更新操作或者添加操作的时候需要进行校验;

对于brangId我们需要在添加操作的时候,可以不需要branId,因为采用的是自增长策略,因此不需要有brandId,这时需要校验必须为空值。而对于更新操作的时候,我们需要brandId,这时需要校验是否是空值。

2.1 @Validated与@Valid

@Validated与@Valid的比较

@Validated:
Spring提供的
支持分组校验
可以用在类型、方法和方法参数上。但是不能用在成员属性(字段)上
由于无法加在成员属性(字段)上,所以无法单独完成级联校验,需要配合@Valid
@Valid:
JDK提供的(标准JSR-303规范)
不支持分组校验
可以用在方法、构造函数、方法参数和成员属性(字段)上
可以加在成员属性(字段)上,能够独自完成级联校验

2.2 定义接口,充当标识

1、添加AddGroup与UpdateGroup接口

package com.zhuang.common.valid; /*** @author mrzhuang* @date 2022/4/25 9:18 PM */ public interface AddGroup { } package com.zhuang.common.valid; /*** @author mrzhuang* @date 2022/4/25 9:18 PM */ public interface UpdateGroup { }

2、给属性上添加分组

/*** 品牌id*/@NotNull(message = "更新操作必须填写brandId!", groups = {UpdateGroup.class})@Null(message = "添加操作必须为空值!", groups = {AddGroup.class})@TableIdprivate Long brandId;

3、controller类中的方法
添加操作

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

更新操作

@RequestMapping("/update")public R update(@RequestBody @Validated({UpdateGroup.class}) BrandEntity brand){brandService.updateById(brand);return R.ok();}
2.3 测试

添加操作:
输入的jason字符串:

{"brandId":"2000","name":"","logo":"123","sort":-1,"firstLetter":"xx"}

结果:

{"msg": "参数校验异常","code": 10001,"data": {"brandId": "添加操作必须为空值!"} }

更新操作:
输入的jason字符串:

{"brandId":"","name":"","logo":"123","sort":-1,"firstLetter":"xx"} 结果: {"msg": "参数校验异常","code": 10001,"data": {"brandId": "更新操作必须填写brandId!"} }

注意:
从上面结果中看出@Validated注解无法校验属性上定义的规则!,而且当方法中同时使用@Validated与@Valid时,只会生效左边的第一个,也就是谁靠左就生效谁!!!
原因是:
没有加上默认分组 Default.class。
解决方法:
1、@Validated({UpdateGroup.class)中的分组中加上默认分组
@Validated({AddGroup.class,Default.class})
@Validated({UpdateGroup.class, Default.class})
2、在写的充当标识的接口时继承默认分组
public interface AddGroup extends Default{
}
public interface UpdateGroup extends Default{
}

修改后的方法:
添加操作

@RequestMapping("/save")public R save(@RequestBody @Validated({AddGroup.class,Default.class}) BrandEntity brand){brandService.save(brand);return R.ok();}

更新操作

@RequestMapping("/update")public R update(@RequestBody @Validated({UpdateGroup.class, Default.class}) BrandEntity brand){brandService.updateById(brand);return R.ok();}

3、其他的校验规则

3.1 组序列

指定组与组之间的检验顺序,如果第一个组校验没过,就不会校验后面的组

@GroupSequence({UpdateGroup.class, AddGroup.class, Default.class}) public interface DefaultGroupSequence { }

注意: 需要重新自定义一个接口。

3.2 级联验证

一个待验证的JavaBean类,其中又包含了一个待验证的对象。
需要在待验证的对象属性上加@Valid注解

三、自定义校验规则

/*** 显示状态[0-不显示;1-显示]*/private Integer showStatus;

我们需要在showStatus属性上添加一个自定义校验规则@ListValue:输入的值只能为0或1

1、编写自定义注解

创建自定义注解ListValue,可以参考JSR303已有的注解的内容。
mall-common/src/main/java/com/zhuang/common/valid/ListValue.java

package com.zhuang.common.valid;import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*;/*** @author mrzhuang* @date 2022/4/26 10:23 AM*/ @Documented //约束校验方式 @Constraint(validatedBy = {ListValueConstraintValidator.class} ) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface ListValue {String message() default "{com.chenxin.gulimail.common.valid.ListValue.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};//定义的值数组int[] vals() default {};}

我们还需要指定校验方式ListValueConstraintValidator:校验我们输入的值是否符合要求

@Constraint(validatedBy = {ListValueConstraintValidator.class} )

创建ListValueConstraintValidator类,mall-common/src/main/java/com/zhuang/common/valid/ListValueConstraintValidator.java

查看validatedBy源码,可以看出validatedBy中的元素是ConstraintValidator<?, ?>的子类。

@Documented @Target({ ANNOTATION_TYPE }) @Retention(RUNTIME) public @interface Constraint {/*** {@link ConstraintValidator} classes implementing the constraint. The given classes* must reference distinct target types for a given {@link ValidationTarget}. If two* {@code ConstraintValidator}s refer to the same type, an exception will occur.* <p>* At most one {@code ConstraintValidator} targeting the array of parameters of* methods or constructors (aka cross-parameter) is accepted. If two or more* are present, an exception will occur.** @return array of {@code ConstraintValidator} classes implementing the constraint*/Class<? extends ConstraintValidator<?, ?>>[] validatedBy(); }

因此,我们写ListValueConstraintValidator类时,需要实现ConstraintValidator<?, ?>。

查看ConstraintValidator源码:

public interface ConstraintValidator<A extends Annotation, T> {/*** Initializes the validator in preparation for* {@link #isValid(Object, ConstraintValidatorContext)} calls.* The constraint annotation for a given constraint declaration* is passed.* <p>* This method is guaranteed to be called before any use of this instance for* validation.* <p>* The default implementation is a no-op.** @param constraintAnnotation annotation instance for a given constraint declaration*/default void initialize(A constraintAnnotation) {}/*** Implements the validation logic.* The state of {@code value} must not be altered.* <p>* This method can be accessed concurrently, thread-safety must be ensured* by the implementation.** @param value object to validate* @param context context in which the constraint is evaluated** @return {@code false} if {@code value} does not pass the constraint*/boolean isValid(T value, ConstraintValidatorContext context); }

从上可以看出,ConstraintValidator中的泛型A为ListValue注解,T为获得的数据值value。

因此ListValueConstraintValidator类的内容为:

package com.zhuang.common.valid;import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set;/*** @author mrzhuang* @date 2022/4/26 10:23 AM*/ public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {Set<Integer> set = new HashSet<Integer>();/*** 初始化* @param constraintAnnotation*/@Overridepublic void initialize(ListValue constraintAnnotation) {int[] vals = constraintAnnotation.vals();for (int val : vals) {set.add(val);}}/*** 真正的校验规则* 判断是否校验成功* @param integer* @param constraintValidatorContext* @return*/@Overridepublic boolean isValid(Integer integer, ConstraintValidatorContext constraintValidatorContext) {//判断输入的值是否是规定的值return set.contains(integer);} }

2、编写配置文件ValidationMessages.properties

编写配置文件,输出message信息
mall-common/src/main/resources/ValidationMessages.properties
内容为:

com.zhuang.common.valid.ListValue.message=必须提交指定的值

3、测试

1、加上自定义的@ListValue注解

/*** 显示状态[0-不显示;1-显示]*/@ListValue(vals = {0, 1})private Integer showStatus;

2、输入的jason字符串:

{"brandId":"","name":"小米","logo":"https://gss0.baidu.com/7LsWdDW5_xN3otqbppnN2DJv/forum/pic/item/fd039245d688d43f394f6821381ed21b0ff43b7b.jpg","sort":1,"showStatus":"2","firstLetter":"x"}

showStatus为2时,不是指定的值!校验错误!
结果:

{"msg": "参数校验异常","code": 10001,"data": {"showStatus": "必须提交指定的值"} }

当设置showStatus为0或者1时,校验成功!

{"msg": "success","code": 200 }

总结

以上是生活随笔为你收集整理的JSR303校验的简单使用以及自定义校验规则的代码编写的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。