SpringBoot使用Validation校验参数的详细过程
作者:justry_deng
JSR(Java Specification Requests)是Java界的重要标准;JSR又细分很多标准,其中JSR303就代表Bean Validation。更多细节可参考:https://jcp.org/en/jsr/detail?id=303。
准备工作
引入相关依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
注:本人测试时,还引入了lombok、SpringBoot的web、test等基础依赖,这里就不一一给出了。
约束性注解(简单)说明
注解 | 功能 |
@AssertFalse | 可以为null,如果不为null的话必须为false |
@AssertTrue | 可以为null,如果不为null的话必须为true |
@DecimalMax | 设置不能超过最大值 |
@DecimalMin | 设置不能超过最小值 |
@Digits | 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内 |
@Future | 日期必须在当前日期的未来 |
@Past | 日期必须在当前日期的过去 |
@Max | 最大不得超过此最大值 |
@Min | 最大不得小于此最小值 |
@NotNull | 不能为null,可以是空 |
@Null | 必须为null |
@Pattern | 必须满足指定的正则表达式 |
@Size | 集合、数组、map等的size()值必须在指定范围内 |
必须是email格式 | |
@Length | 长度必须在指定范围内 |
@NotBlank | 字符串不能为null,字符串trim()后也不能等于“” |
@NotEmpty | 不能为null,集合、数组、map等size()不能为0;字符串trim()后可以等于“” |
@Range | 值必须在指定范围内 |
@URL | 必须是一个URL |
注:此表格只是简单的对注解功能的说明,并没有对每一个注解的属性进行说明;可详见源码。
下面简单测试一下上述各注解:
测试所用模型为:
import lombok.Getter; import lombok.Setter; import org.hibernate.validator.constraints.Length; import org.hibernate.validator.constraints.Range; import org.hibernate.validator.constraints.URL; import javax.validation.constraints.*; import java.util.Date; import java.util.List; import java.util.Map; /** * Validation注解 * * @author JustryDeng * @date 2019/1/15 0:43 */ public class ValidationBeanModel { @Setter @Getter public class AbcAssertFalse { @AssertFalse private Boolean myAssertFalse; } @Setter @Getter public class AbcAssertTrue { @AssertTrue private Boolean myAssertTrue; } @Setter @Getter public class AbcDecimalMax { @DecimalMax(value = "12.3") private String myDecimalMax; } @Setter @Getter public class AbcDecimalMin { @DecimalMin(value = "10.3") private String myDecimalMin; } @Setter @Getter public class AbcDigits { @Digits(integer = 5, fraction = 3) private Integer myDigits; } @Setter @Getter public class AbcEmail { @Email private String myEmail; } @Setter @Getter public class AbcFuture { @Future private Date myFuture; } @Setter @Getter public class AbcLength { @Length(min = 5, max = 10) private String myLength; } @Setter @Getter public class AbcMax { @Max(value = 200) private Long myMax; } @Setter @Getter public class AbcMin { @Min(value = 100) private Long myMin; } @Setter @Getter public class AbcNotBlank { @NotBlank private String myStringNotBlank; @NotBlank private String myObjNotBlank; } @Setter @Getter public class AbcNotEmpty { @NotEmpty private String myStringNotEmpty; @NotEmpty private String myNullNotEmpty; @NotEmpty private Map<String, Object> myMapNotEmpty; @NotEmpty private List<Object> myListNotEmpty; @NotEmpty private Object[] myArrayNotEmpty; } @Setter @Getter public class AbcNotNull { @NotNull private String myStringNotNull; @NotNull private Object myNullNotNull; @NotNull private Map<String, Object> myMapNotNull; } @Setter @Getter public class AbcNull { @Null private String myStringNull; @Null private Map<String, Object> myMapNull; } @Setter @Getter public class AbcPast { @Past private Date myPast; } @Setter @Getter public class AbcPattern { @Pattern(regexp = "\\d+") private String myPattern; } @Setter @Getter public class AbcRange { @Range(min = 100, max = 100000000000L) private Double myRange; } @Setter @Getter public class AbcSize { @Size(min = 3, max = 5) private List<Integer> mySize; } @Setter @Getter public class AbcURL { @URL private String myURL; } }
测试方法为:
import com.aspire.model.*; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import javax.validation.ConstraintViolation; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import java.util.*; @RunWith(SpringRunner.class) @SpringBootTest public class ValidationDemoApplicationTests { private Validator validator; @Before public void initValidator() { ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory(); validator = validatorFactory.getValidator(); } /** * 在myAssertTrue属性上加@AssertTrue注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcAssertTrue类的myAssertTrue属性 -> 只能为true */ @Test public void testAssertTrue() { ValidationBeanModel.AbcAssertTrue vm = new ValidationBeanModel().new AbcAssertTrue(); vm.setMyAssertTrue(false); fa(vm); } /** * 在myAssertFalse属性上加@AssertFalse注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcAssertFalse类的myAssertFalse属性 -> 只能为false */ @Test public void testAssertFalse() { ValidationBeanModel.AbcAssertFalse vm = new ValidationBeanModel().new AbcAssertFalse(); vm.setMyAssertFalse(true); fa(vm); } /** * 在myDecimalMax属性上加@DecimalMax(value = "12.3")注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcDecimalMax类的myDecimalMax属性 -> 必须小于或等于12.3 */ @Test public void testDecimalMax() { ValidationBeanModel.AbcDecimalMax vm = new ValidationBeanModel().new AbcDecimalMax(); vm.setMyDecimalMax("123"); fa(vm); } /** * 在myDecimalMin属性上加@DecimalMin(value = "10.3")注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcDecimalMin类的myDecimalMin属性 -> 必须大于或等于10.3 */ @Test public void testDecimalMin() { ValidationBeanModel.AbcDecimalMin vm = new ValidationBeanModel().new AbcDecimalMin(); vm.setMyDecimalMin("1.23"); fa(vm); } /** * 在myDigits属性上加@Digits(integer = 5, fraction = 3)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcDigits类的myDigits属性 -> 数字的值超出了允许范围(只允许在5位整数和3位小数范围内) */ @Test public void testDigits() { ValidationBeanModel.AbcDigits vm = new ValidationBeanModel().new AbcDigits(); vm.setMyDigits(1000738); fa(vm); } /** * 在myEmail属性上加@Email注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcEmail类的myEmail属性 -> 不是一个合法的电子邮件地址 */ @Test public void testEmail() { ValidationBeanModel.AbcEmail vm = new ValidationBeanModel().new AbcEmail(); vm.setMyEmail("asd@.com"); fa(vm); } /** * 在myFuture属性上加@Future注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcFuture类的myFuture属性 -> 需要是一个将来的时间 */ @Test public void testFuture() { ValidationBeanModel.AbcFuture vm = new ValidationBeanModel().new AbcFuture(); vm.setMyFuture(new Date(10000L)); fa(vm); } /** * 在myLength属性上加@Length(min = 5, max = 10)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcLength类的myLength属性 -> 长度需要在5和10之间 */ @Test public void testLength() { ValidationBeanModel.AbcLength vm = new ValidationBeanModel().new AbcLength(); vm.setMyLength("abcd"); fa(vm); } /** * 在myMax属性上加@Max(value = 200)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcMax类的myMax属性 -> 最大不能超过200 */ @Test public void testMax() { ValidationBeanModel.AbcMax vm = new ValidationBeanModel().new AbcMax(); vm.setMyMax(201L); fa(vm); } /** * 在myMin属性上加@Min(value = 200)注解 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcMin类的myMin属性 -> 最小不能小于100 */ @Test public void testMin() { ValidationBeanModel.AbcMin vm = new ValidationBeanModel().new AbcMin(); vm.setMyMin(99L); fa(vm); } /** * 在myStringNotBlank属性上加@NotBlank注解 * 在myObjNotBlank属性上加@NotBlank注解 * * 注:如果属性值为null 或者 .trim()后等于"",那么会提示 不能为空 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotBlank类的myObjNotBlank属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotBlank类的myStringNotBlank属性 -> 不能为空 */ @Test public void testNotBlank() { ValidationBeanModel.AbcNotBlank vm = new ValidationBeanModel().new AbcNotBlank(); vm.setMyObjNotBlank(null); vm.setMyStringNotBlank(" "); fa(vm); } /** * 在myStringNotEmpty属性上加@NotEmpty注解 * 在myNullNotEmpty属性上加@NotEmpty注解 * 在myMapNotEmpty属性上加@NotEmpty注解 * 在myListNotEmpty属性上加@NotEmpty注解 * 在myArrayNotEmpty属性上加@NotEmpty注解 * * 注:String可以是.trim()后等于""的字符串,但是不能为null * 注:MAP、Collection、Array既不能是空,也不能是null * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myNullNotEmpty属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myListNotEmpty属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myArrayNotEmpty属性 -> 不能为空 * com.aspire.model.ValidationBeanModel$AbcNotEmpty类的myMapNotEmpty属性 -> 不能为空 */ @Test public void testNotEmpty() { ValidationBeanModel.AbcNotEmpty vm = new ValidationBeanModel().new AbcNotEmpty(); vm.setMyStringNotEmpty(" "); vm.setMyNullNotEmpty(null); vm.setMyMapNotEmpty(new HashMap<>(0)); vm.setMyListNotEmpty(new ArrayList<>(0)); vm.setMyArrayNotEmpty(new String[]{}); fa(vm); } /** * 在myStringNotNull属性上加@NotNull注解 * 在myNullNotNull属性上加@NotNull注解 * 在myMapNotNull属性上加@NotNull注解 * * 注:属性值可以是空的, 但是就是不能为null * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNotNull类的myNullNotNull属性 -> 不能为null */ @Test public void testNotNull() { ValidationBeanModel.AbcNotNull vm = new ValidationBeanModel().new AbcNotNull(); vm.setMyStringNotNull(" "); vm.setMyNullNotNull(null); vm.setMyMapNotNull(new HashMap<>(0)); fa(vm); } /** * 在myStringNull属性上加@Null注解 * 在myMapNotNull属性上加@Null注解 * * 注:属性值必须是null, 是空都不行 * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcNull类的myMapNull属性 -> 必须为null * com.aspire.model.ValidationBeanModel$AbcNull类的myStringNull属性 -> 必须为null */ @Test public void testNull() { ValidationBeanModel.AbcNull vm = new ValidationBeanModel().new AbcNull(); vm.setMyStringNull(" "); vm.setMyMapNull(new HashMap<>(0)); fa(vm); } /** * 在myPast属性上加@Past注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcPast类的myPast属性 -> 需要是一个过去的时间 */ @Test public void testPast() { ValidationBeanModel.AbcPast vm = new ValidationBeanModel().new AbcPast(); vm.setMyPast(new Date(20000000000000000L)); fa(vm); } /** * 在myPattern属性上加@Pattern(regexp = "\\d+")注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcPattern类的myPattern属性 -> 需要匹配正则表达式"\d" */ @Test public void testPattern() { ValidationBeanModel.AbcPattern vm = new ValidationBeanModel().new AbcPattern(); vm.setMyPattern("ABC"); fa(vm); } /** * 在myRange属性上加@Range(min = 100, max = 100000000000L)注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcRange类的myRange属性 -> 需要在100和100000000000之间 */ @Test public void testRange() { ValidationBeanModel.AbcRange vm = new ValidationBeanModel().new AbcRange(); vm.setMyRange(32222222222222222222222222222222.323); fa(vm); } /** * 在mySize属性上加@Size(min = 3, max = 5)注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcSize类的mySize属性 -> 个数必须在3和5之间 */ @Test public void testSize() { ValidationBeanModel.AbcSize vm = new ValidationBeanModel().new AbcSize(); List<Integer> list = new ArrayList<>(4); list.add(0); list.add(1); vm.setMySize(list); fa(vm); } /** * 在myURL属性上加@URL注解 * * <p> * 程序输出: com.aspire.model.ValidationBeanModel$AbcURL类的myURL属性 -> 需要是一个合法的URL */ @Test public void testURL() { ValidationBeanModel.AbcURL vm = new ValidationBeanModel().new AbcURL(); vm.setMyURL("www.baidu.xxx"); fa(vm); } private <T> void fa(T obj) { Set<ConstraintViolation<T>> cvSet = validator.validate(obj); for (ConstraintViolation<T> cv : cvSet) { System.err.println(cv.getRootBean().getClass().getName() + "类的" + cv.getPropertyPath() + "属性 -> " + cv.getMessage()); } } }
@Validated的使用时机
@Validated的使用位置较多(可详见源码),但其主流的使用位置却是以下两种:
1、在Controller层中,放在模型参数对象前。
当Controller层中参数是一个对象模型时,只有将@Validated直接放在该模型前,该模型内部的字段才会被
校验(如果有对该模型的字段进行约束的话)。
2、在Controller层中,放在类上。
当一些约束是直接出现在Controller层中的参数前时,只有将@Validated放在类上时,参数前的约束才会生效。
以下是简单的测试代码:
import com.aspire.model.ValidationBeanModel; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.constraints.DecimalMax; /** * Controller层 --- 初步简单测试 @Validated 的使用位置 * * 对比测试过程: * 方案一 : 不在类上加@Validated注解,访问这六个接口 * 方案二 : 在类上加@Validated注解,再次访问这六个接口 * * 对比方案一和方案二,可初步得出@Validated的使用时机: * 1.当我们是在模型里面对模型字段添加约束注解,在Controller中使用模型接收数 * 据时,@Validated要直接放在该模型参数前才有效。 如: "/test/one" * 2.当我们是直接在Controller层中的参数前,使用约束注解时,@Validated要直接放在类上, * 才会有效。如: /test/six * * * @author JustryDeng * @date 2019/1/18 22:22 */ @RestController @Validated public class JustryDengController { @RequestMapping(value = "/test/one") public String validatioOne(@Validated ValidationBeanModel.AbcDecimalMax myDecimalMax) { System.out.println(myDecimalMax.getMyDecimalMax()); return "one pass!"; } @RequestMapping(value = "/test/two") @Validated public String validatioTwo(ValidationBeanModel.AbcDecimalMax myDecimalMax) { System.out.println(myDecimalMax.getMyDecimalMax()); return "two pass!"; } @RequestMapping(value = "/test/three") public String validatioThree(ValidationBeanModel.AbcDecimalMax myDecimalMax) { System.out.println(myDecimalMax.getMyDecimalMax()); return "three pass!"; } @RequestMapping(value = "/test/four") public String validatioFour(@Validated @DecimalMax(value = "12.3") String myDecimalMax) { System.out.println(myDecimalMax); return "four pass!"; } @RequestMapping(value = "/test/five") @Validated public String validatioFive(@DecimalMax(value = "12.3") String myDecimalMax) { System.out.println(myDecimalMax); return "five pass!"; } @RequestMapping(value = "/test/six") @Validated public String validatioSix(@DecimalMax(value = "12.3") String myDecimalMax) { System.out.println(myDecimalMax); return "six pass!"; } }
@Validated与@Valid的简单对比说明
@Valid注解与@Validated注解功能大部分类似;两者的不同主要在于:@Valid属于javax下的,而@Validated属于spring下;@Valid支持嵌套校验、而@Validated不支持,@Validated支持分组,而@Valid不支持。笔者这里只简单介绍@Validated的使用时机。
自定义注解
虽然Bean Validation和Hibernate Validator已经提供了非常丰富的校验注解,但是在实际业务中,难免会碰到一些现有注解不足以校验的情况;这时,我们可以考虑自定义Validation注解。
示例:
第一步:创建自定义注解
import com.aspire.constraints.impl.JustryDengValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.Documented; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * 自定义校验注解 * 提示: * 1、message、contains、payload是必须要写的 * 2、还需要什么方法可根据自己的实际业务需求,自行添加定义即可 * * 注:当没有指定默认值时,那么在使用此注解时,就必须输入对应的属性值 * * @author JustryDeng * @date 2019/1/15 1:17 */ @Target({FIELD, PARAMETER}) @Retention(RUNTIME) @Documented // 指定此注解的实现,即:验证器 @Constraint(validatedBy ={JustryDengValidator.class}) public @interface ConstraintsJustryDeng { // 当验证不通过时的提示信息 String message() default "JustryDeng : param value must contais specified value!"; // 根据实际需求定的方法 String contains() default ""; // 约束注解在验证时所属的组别 Class<?>[] groups() default { }; // 负载 Class<? extends Payload>[] payload() default { }; }
第二步:编写(第一步中的校验器实现类)该注解
import com.aspire.constraints.anno.ConstraintsJustryDeng; import org.hibernate.validator.internal.engine.ValidationContext; import org.hibernate.validator.internal.engine.ValueContext; import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintTree; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; /** * ConstraintsJustryDeng注解 校验器 实现 * <p> * 注:验证器需要实现ConstraintValidator<U, V>, 其中 U为对应的注解类, V为被该注解标记的字段的类型(或其父类型) * * 注: 当项目启动后,会(懒加载)创建ConstraintValidator实例,在创建实例后会初始化调 * 用{@link ConstraintValidator#initialize}方法。 * 所以, 只有在第一次请求时,会走initialize方法, 后面的请求是不会走initialize方法的。 * * 注: (懒加载)创建ConstraintValidator实例时, 会走缓存; 如果缓存中有,则直接使用相 * 同的ConstraintValidator实例; 如果缓存中没有,那么会创建新的ConstraintValidator实例。 * 由于缓存的key是能唯一定位的, 且 ConstraintValidator的实例属性只有在 * {@link ConstraintValidator#initialize}方法中才会写;在{@link ConstraintValidator#isValid} * 方法中只是读。 * 所以不用担心线程安全问题。 * * 注: 如何创建ConstraintValidator实例的,可详见源码 * @see ConstraintTree#getInitializedConstraintValidator(ValidationContext, ValueContext) * * @author JustryDeng * @date 2019/1/15 1:19 */ public class JustryDengValidator implements ConstraintValidator<ConstraintsJustryDeng, Object> { /** 错误提示信息 */ private String contains; /** * 初始化方法, 在(懒加载)创建一个当前类实例后,会马上执行此方法 * * 注: 此方法只会执行一次,即:创建实例后马上执行。 * * @param constraintAnnotation * 注解信息模型,可以从该模型中获取注解类中定义的一些信息,如默认值等 * @date 2019/1/19 11:27 */ @Override public void initialize(ConstraintsJustryDeng constraintAnnotation) { System.out.println(constraintAnnotation.message()); this.contains = constraintAnnotation.contains(); } /** * 校验方法, 每个需要校验的请求都会走这个方法 * * 注: 此方法可能会并发执行,需要根据实际情况看否是需要保证线程安全。 * * @param value * 被校验的对象 * @param context * 上下文 * * @return 校验是否通过 */ @Override public boolean isValid(Object value, ConstraintValidatorContext context) { if (value == null) { return false; } if (value instanceof String) { String strMessage = (String) value; return strMessage.contains(contains); } else if (value instanceof Integer) { return contains.contains(String.valueOf(value)); } return false; } }
第三步:自定义注解简单使用测试
Controller层中是这样的:
访问一下这个接口(当参数值符合要求时):
访问一下这个接口(当参数值不符合要求时):
对注解抛出的异常进行处理
说明:当注解校验不通过时,直接将异常信息返回给前端其实并不友好,我们可以将异常包装一下,返回给前端。
情况一:使用BindingResult类来容纳异常信息,当校验不通过时,不影响正常程
序往下走。我们只需要处理BindingResult中的异常信息即可。
处理前:
参数模型是这样的:
Controller是这样的:
使用postman测试,当校验不通过时显示如下:
处理后:
参数模型是这样的(没有变):
Controller是这样的(在@Validated注解的参数后,紧接着加上BindingResult):
再次使用postman测试,当校验不通过时显示如下:
postman中返回了数据,说明虽然参数错误,但是不影响程序的执行。
程序在控制台输出了如下信息:
可见,后台已经获悉了错误,至于怎么处理,这就看伟大的程序员们了。
情况二(推荐):通过SpringMVC全局异常处理器来处理异常。
描述:如果不采用BindingResult来容纳异常信息时,那么异常会被向外抛出。注解校验不通过时,可能抛出的 异常有BindException异常、ValidationException异常(或其子类异常)、 MethodArgumentNotValidException异常。
处理前:
示例一:Controller和对应的参数模型是这样的:
使用postman测试,当校验不通过时显示如下:
示例二:Controller是这样的:
使用postman测试,当校验不通过时显示如下:
注:ConstraintViolationException异常是ViolationException异常的子异常。
示例三:Controller是这样的:
注:此处的User模型与情况一里给出的模型是一样的,这里就不再给出了。
使用postman测试,当校验不通过时显示如下:
进行处理:加入AOP全局异常处理器:
import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.validation.BindException; import org.springframework.validation.BindingResult; import org.springframework.validation.FieldError; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.validation.ConstraintViolationException; import javax.validation.ValidationException; import java.util.HashMap; import java.util.Map; /** * SpringMVC统一异常处理 * * @author JustryDeng * @date 2019/10/12 16:28 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { /** * 处理Validated校验异常 * <p> * 注: 常见的ConstraintViolationException异常, 也属于ValidationException异常 * * @param e * 捕获到的异常 * @return 返回给前端的data */ @ResponseStatus(code = HttpStatus.BAD_REQUEST) @ExceptionHandler(value = {BindException.class, ValidationException.class, MethodArgumentNotValidException.class}) public Map<String, Object> handleParameterVerificationException(Exception e) { log.error(" handleParameterVerificationException has been invoked", e); Map<String, Object> resultMap = new HashMap<>(4); resultMap.put("code", "100001"); String msg = null; if (e instanceof MethodArgumentNotValidException) { BindingResult bindingResult = ((MethodArgumentNotValidException) e).getBindingResult(); // getFieldError获取的是第一个不合法的参数(P.S.如果有多个参数不合法的话) FieldError fieldError = bindingResult.getFieldError(); if (fieldError != null) { msg = fieldError.getDefaultMessage(); } } else if (e instanceof BindException) { // getFieldError获取的是第一个不合法的参数(P.S.如果有多个参数不合法的话) FieldError fieldError = ((BindException) e).getFieldError(); if (fieldError != null) { msg = fieldError.getDefaultMessage(); } } else if (e instanceof ConstraintViolationException) { /* * ConstraintViolationException的e.getMessage()形如 * {方法名}.{参数名}: {message} * 这里只需要取后面的message即可 */ msg = e.getMessage(); if (msg != null) { int lastIndex = msg.lastIndexOf(':'); if (lastIndex >= 0) { msg = msg.substring(lastIndex + 1).trim(); } } /// ValidationException 的其它子类异常 } else { msg = "处理参数时异常"; } resultMap.put("msg", msg); return resultMap; } }
处理后,使用postman再次进行上述两个请求:
可见,异常处理成功!
【补充】多级嵌套模型的校验
假设:
有api:
有Company和Employee类:
那么,当Department里面既有普通字段,又有类字段时:
解决方式是,给复杂对象字段加上@Valid注解:
【补充】groups分组校验
在很多时候,同一个模型可能会在多处被用到,但每处的校验场景又不一定相同(如:新增用户接口、修改用户接口,参数都是User模型,在新增时User中name字段不能为空,userNo字段可以为空;在修改时User中name字段可以为空,userNo字段不能为空)。我们可以用groups来实现:同一个模型在不同场景下,(动态区分)校验模型中的不同字段。
提示:实现groups功能的主要逻辑源码,可详见org.hibernate.validator.internal.engine.ValidatorImpl#validate。
使用方式(示例说明)
准备工作:自定义两个分组。
提示:继承Default并不是必须的。只是说,如果继承了Default,那么@Validated(value = Create.class)的校验范畴就
为【Create】和【Default】;
如果没继承Default,那么@Validated(value = Create.class)的校验范畴只
为【Create】,而@Validated(value = {Create.class, Default.class})的校验范畴才为【Create】和【Default】。
追注:原因可见下面的第二步中的说明。
第一步:(在给模型里面的参数定义校验规则时,)给校验分配所属分组。
注:Default组和无参构造机制类似,当没有指定分组时,会默认当前校验属于Default组,但是一旦主动给当前校验指定
了分组(如上图中的name字段,主动指定了属于Create组),那么就不会再额外指定属于Default组了。
追注:当然,也可以画蛇添足的主动指定所属分组为Default。
第二步:启动校验时,指定校验(组别)范畴。
为更直观的理解,再给出一张图:
注:还有一个用于联合校验(如:字段A的校验,依赖于字段B的校验)等场景的注解javax.validation.GroupSequence 有时也非常实用,感兴趣的可自行了解。
到此这篇关于SpringBoot使用Validation校验参数的文章就介绍到这了,更多相关SpringBoot使用Validation校验参数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
您可能感兴趣的文章:
- SpringBoot使用validation-api实现参数校验的示例
- SpringBoot集成validation校验参数遇到的坑
- SpringBoot集成Validation参数校验
- SpringBoot使用validation做参数校验说明
- SpringBoot 中使用 Validation 校验参数的方法详解
- SpringBoot利用validation实现优雅的校验参数
- SpringBoot Validation提示信息国际化配置方式
- SpringBoot使用Validation进行参数校验的示例详解
- springboot中使用Hibernate-Validation校验参数详解
- SpringBoot Validation入参校验国际化的项目实践