3.JSR303校验 + 统一异常处理方法
问题引入:填写form时应该有前端校验,后端也应该有校验
对于后端来说,外界传递的一切参数都不可靠。
前端的校验是element-ui表单验证https://element.eleme.cn/#/zh-CN/component/form
Form 组件提供了表单验证的功能,只需要通过 rules 属性传入约定的验证规则,并将 Form-Item 的 prop 属性设置为需校验的字段名即可。校验规则参见 async-validator
使用自定义校验规则可以解决字母限制的问题
var validatePass2 = (rule, value, callback) => {
if (value === '') {
callback(new Error('请再次输入密码'));
} else if (value !== this.ruleForm.pass) {
callback(new Error('两次输入密码不一致!'));
} else {
callback();
}
};
return {
rules: {
checkPass: [
{ validator: validatePass2, trigger: 'blur' }
],
后端:@NotNull等
1.@NotNull等
步骤1:使用校验注解
在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解。
<!--jsr3参数校验器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
里面依赖了hibernate-validator
在非空处理方式上提供了@NotNull,@NotBlank和@NotEmpty
(1)@NotNull 该属性不能为null
(2)@NotEmpty 该字段不能为null或""
支持以下几种类型
1.CharSequence (length of character sequence is evaluated)字符序列(字符序列长度的计算)
2.Collection (collection size is evaluated) 集合长度的计算
3.Map (map size is evaluated) map长度的计算
4.Array (array length is evaluated) 数组长度的计算
5.上面什么意思呢?就是说如果标注的是map,它会帮你看长度
(3)@NotBlank:不能为空,不能仅为一个空格
2.@Valid内置异常
这里内置异常的意思是发生异常时返回的json不是我们的R对象,而是mvc的内置类
步骤2:controller中加校验注解@Valid,开启校验,
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
测试: http://localhost:88/api/product/brand/save
在postman种发送上面的请求,可以看到返回的甚至不是R对象
{
"timestamp": "2020-04-29T09:20:46.383+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.brandEntity.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"brandEntity.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "不能为空",
"objectName": "brandEntity",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='brandEntity'. Error count: 1",
"path": "/product/brand/save"
}
能够看到"defaultMessage": “不能为空”,这些错误消息定义在“hibernate-validator”的“\org\hibernate\validator\ValidationMessages_zh_CN.properties”文件中。在该文件中定义了很多的错误规则:
javax.validation.constraints.AssertFalse.message = 只能为false
javax.validation.constraints.AssertTrue.message = 只能为true
javax.validation.constraints.DecimalMax.message = 必须小于或等于{value}
javax.validation.constraints.DecimalMin.message = 必须大于或等于{value}
javax.validation.constraints.Digits.message = 数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)
javax.validation.constraints.Email.message = 不是一个合法的电子邮件地址
javax.validation.constraints.Future.message = 需要是一个将来的时间
javax.validation.constraints.FutureOrPresent.message = 需要是一个将来或现在的时间
javax.validation.constraints.Max.message = 最大不能超过{value}
javax.validation.constraints.Min.message = 最小不能小于{value}
javax.validation.constraints.Negative.message = 必须是负数
javax.validation.constraints.NegativeOrZero.message = 必须是负数或零
javax.validation.constraints.NotBlank.message = 不能为空
javax.validation.constraints.NotEmpty.message = 不能为空
javax.validation.constraints.NotNull.message = 不能为null
javax.validation.constraints.Null.message = 必须为null
javax.validation.constraints.Past.message = 需要是一个过去的时间
javax.validation.constraints.PastOrPresent.message = 需要是一个过去或现在的时间
javax.validation.constraints.Pattern.message = 需要匹配正则表达式"{regexp}"
javax.validation.constraints.Positive.message = 必须是正数
javax.validation.constraints.PositiveOrZero.message = 必须是正数或零
javax.validation.constraints.Size.message = 个数必须在{min}和{max}之间
org.hibernate.validator.constraints.CreditCardNumber.message = 不合法的信用卡号码
org.hibernate.validator.constraints.Currency.message = 不合法的货币 (必须是{value}其中之一)
org.hibernate.validator.constraints.EAN.message = 不合法的{type}条形码
org.hibernate.validator.constraints.Email.message = 不是一个合法的电子邮件地址
org.hibernate.validator.constraints.Length.message = 长度需要在{min}和{max}之间
org.hibernate.validator.constraints.CodePointLength.message = 长度需要在{min}和{max}之间
org.hibernate.validator.constraints.LuhnCheck.message = ${validatedValue}的校验码不合法, Luhn模10校验和不匹配
org.hibernate.validator.constraints.Mod10Check.message = ${validatedValue}的校验码不合法, 模10校验和不匹配
org.hibernate.validator.constraints.Mod11Check.message = ${validatedValue}的校验码不合法, 模11校验和不匹配
org.hibernate.validator.constraints.ModCheck.message = ${validatedValue}的校验码不合法, ${modType}校验和不匹配
org.hibernate.validator.constraints.NotBlank.message = 不能为空
org.hibernate.validator.constraints.NotEmpty.message = 不能为空
org.hibernate.validator.constraints.ParametersScriptAssert.message = 执行脚本表达式"{script}"没有返回期望结果
org.hibernate.validator.constraints.Range.message = 需要在{min}和{max}之间
org.hibernate.validator.constraints.SafeHtml.message = 可能有不安全的HTML内容
org.hibernate.validator.constraints.ScriptAssert.message = 执行脚本表达式"{script}"没有返回期望结果
org.hibernate.validator.constraints.URL.message = 需要是一个合法的URL
org.hibernate.validator.constraints.time.DurationMax.message = 必须小于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
org.hibernate.validator.constraints.time.DurationMin.message = 必须大于${inclusive == true ? '或等于' : ''}${days == 0 ? '' : days += '天'}${hours == 0 ? '' : hours += '小时'}${minutes == 0 ? '' : minutes += '分钟'}${seconds == 0 ? '' : seconds += '秒'}${millis == 0 ? '' : millis += '毫秒'}${nanos == 0 ? '' : nanos += '纳秒'}
想要自定义错误消息,可以覆盖默认的错误提示信息,如@NotBlank的默认message是
public @interface NotBlank {
String message() default "{javax.validation.constraints.NotBlank.message}";
可以在添加注解的时候,修改message:
@NotBlank(message = "品牌名必须非空")
private String name;
当再次发送请求时,得到的错误提示信息:
{
"timestamp": "2020-04-29T09:36:04.125+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.brandEntity.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"brandEntity.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
"defaultMessage": "品牌名必须非空",
"objectName": "brandEntity",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='brandEntity'. Error count: 1",
"path": "/product/brand/save"
}
但是返回的错误不是R对象,影响接收端的接收,我们可以通过局部异常处理或者统一一次处理解决
3.局部异常处理BindResult
步骤3:给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。
如下两个方法是一体的
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand){
brandService.save(brand);
return R.ok();
}
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand,
BindingResult result){ // 手动处理异常
if( result.hasErrors()){
Map<String,String> map=new HashMap<>();
//1.获取错误的校验结果
result.getFieldErrors().forEach((item)->{
//获取发生错误时的message
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();
这种是针对于该请求设置了一个内容校验,如果针对于每个请求都单独进行配置,显然不是太合适,实际上可以统一的对于异常进行处理。
4.统一异常处理@ExceptionHandler
上文说到 @ ExceptionHandler 需要进行异常处理的方法必须与出错的方法在同一个Controller里面。那么当代码加入了 @ControllerAdvice,则不需要必须在同一个 controller 中了。这也是 Spring 3.2 带来的新特性。从名字上可以看出大体意思是控制器增强。 也就是说,@controlleradvice + @ ExceptionHandler 也可以实现全局的异常捕捉。
(1)抽取一个异常处理类
@ControllerAdvice标注在类上,通过“basePackages”能够说明处理哪些路径下的异常。
@ExceptionHandler(value = 异常类型.class)标注在方法上
@Slf4j
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")//管理的controller
public class GulimallExceptionControllerAdvice {
@ExceptionHandler(value = Exception.class) // 也可以返回ModelAndView
public R handleValidException(MethodArgumentNotValidException exception){
Map<String,String> map=new HashMap<>();
// 获取数据校验的错误结果
BindingResult bindingResult = exception.getBindingResult();
// 处理错误
bindingResult.getFieldErrors().forEach(fieldError -> {
String message = fieldError.getDefaultMessage();
String field = fieldError.getField();
map.put(field,message);
});
log.error("数据校验出现问题{},异常类型{}",exception.getMessage(),exception.getClass());
return R.error(400,"数据校验出现问题").put("data",map);
}
}
(2)测试: http://localhost:88/api/product/brand/save
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZirxsX9l-1642404848092)(D:%5Ccxy%5Cjava%E6%97%A5%E7%A7%AF%E6%9C%88%E7%B4%AF%5C%E6%97%A5%E7%A7%AF%E6%9C%88%E7%B4%AF.assets%5Cimage-20200429183334783.png)]
(3)默认异常处理
@ExceptionHandler(value = Throwable.class)//异常的范围更大
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",
throwable.getMessage(),
throwable.getClass());
return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}
(4)错误状态码
上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
上面的用法主要是通过@Controller+@ExceptionHandler来进行异常拦截处理
BizCodeEnum
为了定义这些错误状态码,我们可以单独定义一个常量类(一般定义为枚举类),用来存储这些错误状态码
package com.atguigu.common.exception;
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*/
public enum BizCodeEnum {
UNKNOW_EXEPTION(10000,"系统未知异常"),
VALID_EXCEPTION( 10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
(5)测试: http://localhost:88/api/product/brand/save
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fJtELzat-1642404848095)(D:%5Ccxy%5Cjava%E6%97%A5%E7%A7%AF%E6%9C%88%E7%B4%AF%5C%E6%97%A5%E7%A7%AF%E6%9C%88%E7%B4%AF.assets%5Cimage-20200429191830967.png)]
可以参考下:https://blog.youkuaiyun.com/github_36086968/article/details/103115128
补充详解:
统一异常处理:
SpringBoot中有一个
@ControllerAdvice
的注解,使用该注解即表示开启全局异常捕获,接下来我们只需在自定义的方法上使用@ExceptionHandler
注解,并定义捕获异常的类型,那么这个自定义的方法体就是对这种类型的异常进行统一的处理。
同时还提供@RestControllerAdvice
注解,就相当于@RestController
。
@RestController
=@Controller
+@ResponseBody
@RestControllerAdvice
=@ControllerAdvice
+@ResponseBody
统一异常处理会优先处理子类异常,也就是说当出现了空指针异常,那么会优先交给handleNullPointerException
这个方法处理,如果没有明确的针对某种异常进行处理,那么handleException
这个方法会自觉的处理它们。
后端无论是怎么报错,返回给前端的永远是JSON数据,之后前端就可以该跳转错误页面的跳转,该弹框警告的弹框!
举例:
/**
* 统一结果响应类
* @param <T>
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result<T> implements Serializable {
private static final long serialVersionUID = 1L;
//业务码,比如成功、失败、权限不足等 code,可自行定义
@ApiModelProperty("返回码")
private int status;
//返回信息,后端在进行业务处理后返回给前端一个提示信息,可自行定义
@ApiModelProperty("返回信息")
private String message;
//数据结果,泛型,可以是列表、单个对象、数字、布尔值等
@ApiModelProperty("返回数据")
private T data;
public Result(int resultCode, String message) {
this.status = resultCode;
this.message = message;
}
/**
* 响应结果生成工具
*/
public class ResultGenerator {
private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
private static final String DEFAULT_FAIL_MESSAGE = "FAIL";
private static final int RESULT_CODE_SUCCESS = 200;
private static final int RESULT_CODE_SERVER_ERROR = 500;
//默认成功返回success
public static Result genSuccessResult() {
Result result = new Result();
result.setStatus(RESULT_CODE_SUCCESS);
result.setMessage(DEFAULT_SUCCESS_MESSAGE);
return result;
}
//自定义成功时的message
public static Result genSuccessResult(String message) {
Result result = new Result();
result.setStatus(RESULT_CODE_SUCCESS);
result.setMessage(message);
return result;
}
//默认成功返回success并且传入返回结果
public static Result genSuccessResult(Object data) {
Result result = new Result();
result.setStatus(RESULT_CODE_SUCCESS);
result.setMessage(DEFAULT_SUCCESS_MESSAGE);
result.setData(data);
return result;
}
//默认失败500状态码,不传入错误消息时默认fail,否则为传入自定义错误消息
public static Result genFailResult(String message) {
Result result = new Result();
result.setStatus(RESULT_CODE_SERVER_ERROR);
if (StringUtils.isEmpty(message)) {
result.setMessage(DEFAULT_FAIL_MESSAGE);
} else {
result.setMessage(message);
}
return result;
}
//自定义错误状态码和消息
public static Result genErrorResult(int code, String message) {
Result result = new Result();
result.setStatus(code);
result.setMessage(message);
return result;
}
}
/**
* 全局异常处理
*/
@RestControllerAdvice
public class MallExceptionHandler {
//一般参数校验绑定异常处理
@ExceptionHandler(BindException.class)
public Object bindException1(BindException e) {
Result result = new Result();
result.setStatus(510);
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
//多个错误,取第一个
FieldError error = fieldErrors.get(0);
String msg = error.getDefaultMessage();
result.setMessage(msg);
return result;
}
//JSON参数校验绑定异常处理
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object bindException2(MethodArgumentNotValidException e) {
Result result = new Result();
result.setStatus(510);
List<FieldError> fieldErrors = e.getBindingResult().getFieldErrors();
//多个错误,取第一个
FieldError error = fieldErrors.get(0);
String msg = error.getDefaultMessage();
result.setMessage(msg);
return result;
}
@ExceptionHandler(Exception.class)
public Object handleException(Exception e, HttpServletRequest req) {
Result result = new Result();
result.setStatus(500);
//区分是否为自定义异常
if (e instanceof MallException) {
result.setMessage(e.getMessage());
//无效认证!请重新登录!||未登录!
if (e.getMessage().equals("未登录!") || e.getMessage().equals("无效认证!请重新登录!")) {
result.setStatus(416);
}else if (e.getMessage().equals("管理员未登录!") || e.getMessage().equals("管理员登录过期!请重新登录!")) {
result.setStatus(419);
}
} else if (e instanceof DuplicateKeyException){
result.setMessage("重复!");
}else {
e.printStackTrace();
result.setMessage("未知异常,请联系管理员");
}
return result;
}
}
5.分组校验功能(多场景校验)
前面解决了统一异常处理,但是现状有新的需求是对同一实体类参数也要区分场景
如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。
步骤:
1.创建分组接口Insert.class Update.class
2.在VO的属性中标注@NotBlank等注解,并指定要使用的分组,如@NotNull(message = “用户姓名不能为空”,groups = {Insert.class,Update.class})
3.controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)
1、@NotNull(groups={A.class})
1、给校验注解,标注上groups,指定什么情况下才需要进行校验
如:指定在更新和添加的时候,都需要进行校验。新增时不需要带id,修改时必须带id
在实体类的统一属性上添加多个不同的校验注解
@NotNull(message = "修改必须定制品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
/*
*
品牌logo地址 修改可以不带上logoURL
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的URL地址", groups={AddGroup.class, UpdateGroup.class})
private String logo;
注意上面因为@NotBlank没有指定UpdateGroup分组,所以不生效。此时update时可以不携带,但带了一定得是url地址
在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。
2、@Validated
业务方法参数上使用@Validated注解
@Validated的value值指定要使用的一个或多个分组
JSR-303 defines validation groups as custom annotations which an application declares for the sole purpose of using
them as type-safe group arguments, as implemented in SpringValidatorAdapter.
JSR-303 将验证组定义为自定义注释,应用程序声明的唯一目的是将它们用作类型安全组参数,如 SpringValidatorAdapter 中实现的那样。
Other SmartValidator implementations may support class arguments in other ways as well.
其他SmartValidator 实现也可以以其他方式支持类参数。
// 新增场景添加 新增分组注解
@RequestMapping("/save")
public R save(@Validated(AddGroup.class) @RequestBody BrandEntity brand) {
brandService.save(brand);
return R.ok();
}
// 删除场景添加 删除分组注解
@RequestMapping("/delete")
public R delete(@RequestBody Long[] brandIds) {
brandService.removeByIds(Arrays.asList(brandIds));
return R.ok();
}
总结:controller接收到之后,根据@Validated表明的分组信息,品牌对应的校验注解。
3、分组校验的默认校验
这里要是指定了分组,实体类上的注解就是指定了分组的注解才生效,
没有指定分组的默认不生效,要是没有指定分组,就是对没有指定分组的注解生效,指定分组的注解就不生效了
可以在自定义的异常分组接口中继承Default类。所有没有写明group的都属于Default分组。
此外还可以在实体类上标注@GroupSequece({A.class,B.class})指定校验顺序
通过@GroupSequence指定验证顺序:先验证A分组,如果有错误立即返回而不会验证B分组,接着如果A分组验证通过了,那么才去验证B分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。
关于Default,此处我springvalidation默认生成的验证接口,验证的范围是所有带有验证信息的属性,
若是属性上方写了验证组,则是验证该组内的属性
若是验证实体类类上写了GroupSequence({}) 则说明重写了Default验证接口,Default就按照GroupSequence里所写的组信息进行验证
6.自定义校验注解
Hibernate Validator提供了一系列内置的校验注解,可以满足大部分的校验需求。但是,仍然有一部分校验需要特殊定制,例如某个字段的校验,我们提供两种校验强度,当为normal强度时我们除了<>号之外,都允许出现。当为strong强度时,我们只允许出现常用汉字,数字,字母。内置的注解对此则无能为力,我们试着通过自定义校验来解决这个问题。
场景:要校验showStatus的0/1状态,可以用正则,但我们可以利用其他方式解决复杂场景。比如我们想要下面的场景
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
添加依赖
<!--校验-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<!--高版本需要javax.el-->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b08</version>
</dependency>
1、自定义校验注解
必须有3个属性:
message()错误信息
groups()分组校验
payload()自定义负载信息
// 自定义注解
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器 关联校验器和校验注解
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 哪都可以标注
@Retention(RUNTIME)
public @interface ListValue {
// 使用该属性去Validation.properties中取
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
// 数组,需要用户自己指定
int[] value() default {};
}
因为上面的message值对应的最终字符串需要去ValidationMessages.properties中获得,所以我们在common中新建文件ValidationMessages.properties
文件内容:
com.atguigu.common.valid.ListValue.message=必须提交指定的值 [0,1]
2、自定义校验器ConstraintValidator
上面只是定义了异常消息,但是怎么验证是否异常还没说,下面的ConstraintValidator就是说的
比如我们要限定某个属性值必须在一个给定的集合里,那么就通过重写initialize()方法,指定可以有哪些元素。
而controller接收到的数据用isValid(验证
public class ListValueConstraintValidator
implements ConstraintValidator<ListValue,Integer> { //<注解,校验值类型>
// 存储所有可能的值
private Set<Integer> set=new HashSet<>();
@Override // 初始化,你可以获取注解上的内容并进行处理
public void initialize(ListValue constraintAnnotation) {
// 获取后端写好的限制
// 这个value就是ListValue里的value,我们写的注解是@ListValue(value={0,1})
int[] value = constraintAnnotation.value();
for (int i : value) {
set.add(i);
}
}
@Override // 覆写验证逻辑
public boolean isValid(Integer value, ConstraintValidatorContext context) {
// 看是否在限制的值里
return set.contains(value);
}
}
具体的校验类需要实现ConstraintValidator接口,第一个泛型参数是所对应的校验注解类型,第二个是校验对象类型。在初始化方法initialize中,我们可以先做一些别的初始化工作,例如这里我们获取到注解上的value并保存下来,然后生成set对象。
真正的验证逻辑由isValid完成,如果传入形参的属性值在这个set里就返回true,否则返回false
3、关联校验器和校验注解
@Constraint(validatedBy = { ListValueConstraintValidator.class})
一个校验注解可以匹配多个校验器
4、使用实例
/**
显示状态[0-不显示;1-显示]
用value[]指定可以写的值
*/
@ListValue(value = {0,1},groups ={AddGroup.class})
private Integer showStatus;
如验证手机号格式,可以参考https://blog.youkuaiyun.com/GAMEloft9/article/details/81699500