欢迎访问 生活随笔!

生活随笔

当前位置: 首页 >

JSR303注解字段校验

发布时间:2024/3/13 42 豆豆
生活随笔 收集整理的这篇文章主要介绍了 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 {@Resourceprivate 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 {@Resourceprivate 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、创建AddGroup和UpdateGroup接口分别表示添加组和更新组

//这俩个接口只是用来标记的,不需要实现 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*/@Overridepublic void initialize(IsUrl constraintAnnotation) {// 接收我们自定义的属性value,默认为""value = constraintAnnotation.value();}/*** //2、逻辑处理* @param s 前端传参的值* @param constraintValidatorContext* @return*/@Overridepublic 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,等后面有空在研究

总结

以上是生活随笔为你收集整理的JSR303注解字段校验的全部内容,希望文章能够帮你解决所遇到的问题。

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