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或空 |
| 该字符串必须是格式正确的电子邮件地址。 | |
| @Null | 注解元素必须是空 |
| @Digits | 带注释的元素必须是可接受范围内的数字 |
| @Future | 带注释的元素必须是未来的瞬间、日期或时间。 |
| @FutureOrPresent | 注释元素必须是当前或未来的瞬间、日期或时间。 |
| @Past | 带注释的元素必须是过去的瞬间、日期或时间。 |
| @PastOrPresent | 带注释的元素必须是过去或现在的瞬间、日期或时间。 |
| @Max | 带注释的元素必须是一个数字,其值必须小于或等于指定的最大值。 |
| @Min | 带注释的元素必须是一个数字,其值必须大于或等于指定的最小值。 |
| @Pattern | 带注释的 {@code CharSequence} 必须匹配指定的正则表达式。正则表达式遵循 Java 正则表达式约定 |
| @Size | 验证元素大小是否在指定范围内 |
| @DecimalMax | 带注释的元素必须是一个数字,其值必须小于或等于指定的最大值。 |
| @DecimalMin | 带注释的元素必须是一个数字,其值必须大于或等于指定的最小值。 |
| @AssertTrue | 被注释的元素必须为true |
| @AssertFalse | 被注释的元素必须为false |
| @Positive | 被注解的元素必须是整数 |
| @PositiveOrZero | 被注解元素必须是正数或0 |
Hibernate validator 在JSR303的基础上对校验注解进行了扩展,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字符串为:
2.3 方法二:写一个异常处理类
对于第一种方法,代码的冗余度比较高。使用统一的异常处理类降低冗余、方便。
1、创建通用的异常枚举类
mall-common/src/main/java/com/zhuang/common/exception/BizCodeEnum.java
2、创建异常类
mall-product/src/main/java/com/zhuang/mall/product/exception/MallExceptionControllerAdvice.java
3、还原初始controller类中的方法
方法中的@Valid注解需要加上!!!
4、测试
输入错误的jason字符串:
输入正确的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("/update")public R update(@RequestBody @Validated({UpdateGroup.class}) BrandEntity brand){brandService.updateById(brand);return R.ok();}2.3 测试
添加操作:
输入的jason字符串:
结果:
{"msg": "参数校验异常","code": 10001,"data": {"brandId": "添加操作必须为空值!"} }更新操作:
输入的jason字符串:
注意:
从上面结果中看出@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("/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
我们还需要指定校验方式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
内容为:
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时,不是指定的值!校验错误!
结果:
当设置showStatus为0或者1时,校验成功!
{"msg": "success","code": 200 }总结
以上是生活随笔为你收集整理的JSR303校验的简单使用以及自定义校验规则的代码编写的全部内容,希望文章能够帮你解决所遇到的问题。
- 上一篇: es查询简单场景问题小记
- 下一篇: JSR303注解字段校验