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检查 | |
---|---|
@AssertTrue | Boolean 成员变量的值只能为 true |
@AssertFalse | Boolean 成员变量的值只能为 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 |
---|---|
@Min | Number 和 String 对象值大于等于指定的值 |
@Max | Number 和 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 | 信用卡验证 |
验证是否是邮件地址,如果为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、创建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
*/
@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,等后面有空在研究