1. JSR303校验 步骤1:使用校验注解 在Java中提供了一系列的校验方式,它这些校验方式在“javax.validation.constraints”包中,提供了如@Email,@NotNull等注解。
在非空处理方式上提供了@NotNull,@Blank和@
(1)@NotNull
The annotated element must not be null. Accepts any type. 注解元素禁止为null,能够接收任何类型
(2)@NotEmpty
the annotated element must not be null nor empty.
该注解修饰的字段不能为null或””
Supported types are:
支持以下几种类型
CharSequence (length of character sequence is evaluated)
字符序列(字符序列长度的计算)
Collection (collection size is evaluated) 集合长度的计算
Map (map size is evaluated) map长度的计算
Array (array length is evaluated) 数组长度的计算
(3)@NotBlank
The annotated element must not be null and must contain at least one non-whitespace character. Accepts CharSequence. 该注解不能为null,并且至少包含一个非空白字符。接收字符序列。
步骤2:在请求方法种,使用校验注解@Valid,开启校验, 1 2 3 4 5 6 @RequestMapping("/save") public R save (@Valid @RequestBody BrandEntity brand) { brandService.save(brand); return R.ok(); }
测试: http://localhost:88/api/product/brand/save
在postman种发送上面的请求
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 { "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”文件中。在该文件中定义了很多的错误规则:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 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是
1 2 3 public @interface NotBlank { String message () default " {javax.validation.constraints.NotBlank.message}";
可以在添加注解的时候,修改message:
1 2 @NotBlank(message = "品牌名必须非空") private String name;
当再次发送请求时,得到的错误提示信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 { "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" }
但是这种返回的错误结果并不符合我们的业务需要。
步骤3:给校验的Bean后,紧跟一个BindResult,就可以获取到校验的结果。拿到校验的结果,就可以自定义的封装。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @RequestMapping("/save") public R save (@Valid @RequestBody BrandEntity brand, BindingResult result) { if ( result.hasErrors()){ Map<String,String> map=new HashMap<>(); result.getFieldErrors().forEach((item)->{ 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:统一异常处理 可以使用SpringMvc所提供的@ControllerAdvice,通过“basePackages”能够说明处理哪些路径下的异常。
(1)抽取一个异常处理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.bigdata.gulimall.product.exception;import com.bigdata.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.ControllerAdvice;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseBody;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.HashMap;import java.util.Map;@Slf4j @RestControllerAdvice(basePackages = "com.bigdata.gulimall.product.controller") public class GulimallExceptionAdvice { @ExceptionHandler(value = Exception.class) 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
(3)默认异常处理
1 2 3 4 5 @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)错误状态码
上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.bigdata.common.exception;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
2. 分组校验功能(完成多场景的复杂校验) 1、给校验注解,标注上groups,指定什么情况下才需要进行校验 如:指定在更新和添加的时候,都需要进行校验
1 2 3 @NotEmpty @NotBlank(message = "品牌名必须非空",groups = {UpdateGroup.class,AddGroup.class}) private String name;
在这种情况下,没有指定分组的校验注解,默认是不起作用的。想要起作用就必须要加groups。
2、业务方法参数上使用@Validated注解 @Validated的value方法:
Specify one or more validation groups to apply to the validation step kicked off by this annotation. 指定一个或多个验证组以应用于此注释启动的验证步骤。
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 实现也可以以其他方式支持类参数。
3、默认情况下,在分组校验情况下,没有指定指定分组的校验注解,将不会生效,它只会在不分组的情况下生效。 3. 自定义校验功能 1、编写一个自定义的校验注解 1 2 3 4 5 6 7 8 9 10 11 12 13 @Documented @Constraint(validatedBy = { ListValueConstraintValidator.class}) @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) @Retention(RUNTIME) public @interface ListValue { String message () default " {com.bigdata.common.valid.ListValue.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; int[] value() default {}; }
2、编写一个自定义的校验器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public class ListValueConstraintValidator implements ConstraintValidator <ListValue ,Integer > { private Set<Integer> set=new HashSet<>(); @Override public void initialize (ListValue constraintAnnotation) { int [] value = constraintAnnotation.value(); for (int i : value) { set.add(i); } } @Override public boolean isValid (Integer value, ConstraintValidatorContext context) { return set.contains(value); } }
3、关联自定义的校验器和自定义的校验注解 1 @Constraint(validatedBy = { ListValueConstraintValidator.class})
4、使用实例 1 2 3 4 5 @ListValue(value = {0,1},groups ={AddGroup.class}) private Integer showStatus;