SpringBoot:使用Validation校验参数以及自定义注解进行参数校验全局异常拦截。

r囧r小猫 2022-11-19 07:45 660阅读 0赞

本文主要包括:基本注解使用及说明,全局异常捕捉,自定义注解的实现,@Validated与@Valid的简单对比及不同实现。

使用 Spring Boot 程序的话只需要spring-boot-starter-web 就够了,它的子依赖包含了我们所需要的东西。除了这个依赖,下面的演示还用到了 lombok ,所以不要忘记添加上相关依赖。

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-web</artifactId>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.projectlombok</groupId>
  8. <artifactId>lombok</artifactId>
  9. <optional>true</optional>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-test</artifactId>
  14. <scope>test</scope>
  15. </dependency>
  16. </dependencies>

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NvdWwwOTI4_size_16_color_FFFFFF_t_70

示例

新建参数类

  1. @Data
  2. public class ValidationModel {
  3. /**
  4. * 主键
  5. */
  6. @NotNull(message = "主键不能为空")
  7. private Long id;
  8. /**
  9. * 名称
  10. */
  11. @NotBlank(message = "名称不能为空")
  12. private String name;
  13. /**
  14. * 邮箱
  15. */
  16. @NotBlank(message = "email 不能为空")
  17. @Email(message = "email 格式不正确")
  18. private String email;
  19. /**
  20. * 年龄
  21. */
  22. @Range(min = 18, max = 60, message = "年龄必须在 {min} 至 {max} 之间")
  23. private String age;
  24. }
  1. 文档地址: https://docs.oracle.com/javaee/7/api/javax/validation/constraints/package-frame.html
  2. https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation

JSR提供的校验注解
































































































注解 说明
@AssertFalse 被注释的元素只能为false
@AssertTrue

被注释的元素只能为true

@DecimalMax 被注释的元素必须小于或等于{value}
@DecimalMin 被注释的元素必须大于或等于{value}
@Digits 被注释的元素数字的值超出了允许范围(只允许在{integer}位整数和{fraction}位小数范围内)
@Email 被注释的元素不是一个合法的电子邮件地址
@Future 被注释的元素需要是一个将来的时间
@FutureOrPresent 被注释的元素需要是一个将来或现在的时间
@Max 被注释的元素最大不能超过{value}
@Min 被注释的元素最小不能小于{value}
@Negative 被注释的元素必须是负数
@NegativeOrZero 被注释的元素必须是负数或零
@NotBlank 被注释的元素不能为空
@NotEmpty 被注释的元素不能为空
@NotNull 被注释的元素不能为null
@Null 被注释的元素必须为null
@Past 被注释的元素需要是一个过去的时间
@PastOrPresent 被注释的元素需要是一个过去或现在的时间
@Pattern 被注释的元素需要匹配正则表达式”{regexp}”
@Positive 被注释的元素必须是正数
@PositiveOrZero 被注释的元素必须是正数或零
@Size 被注释的元素个数必须在{min}和{max}之间

Hibernate Validator提供的校验注解
















































































@CreditCardNumber 被注释的元素不合法的信用卡号码
@Currency 被注释的元素不合法的货币 (必须是{value}其中之一)
@EAN 被注释的元素不合法的{type}条形码
@Email 被注释的元素不是一个合法的电子邮件地址 (已过期)
@Length 被注释的元素长度需要在{min}和{max}之间
@CodePointLength 被注释的元素长度需要在{min}和{max}之间
@LuhnCheck 被注释的元素${validatedValue}的校验码不合法, Luhn模10校验和不匹配
@Mod10Check 被注释的元素${validatedValue}的校验码不合法, 模10校验和不匹配
@Mod11Check 被注释的元素${validatedValue}的校验码不合法, 模11校验和不匹配
@ModCheck 被注释的元素${validatedValue}的校验码不合法, ${modType}校验和不匹配 (已过期)
@NotBlank 被注释的元素不能为空 (已过期)
@NotEmpty 被注释的元素不能为空 (已过期)
@ParametersScriptAssert 被注释的元素执行脚本表达式”{script}”没有返回期望结果
@Range 被注释的元素需要在{min}和{max}之间
@SafeHtml 被注释的元素可能有不安全的HTML内容
@ScriptAssert 被注释的元素执行脚本表达式”{script}”没有返回期望结果
@URL 被注释的元素需要是一个合法的URL
@DurationMax 被注释的元素必须小于${inclusive == true ? ‘或等于’ : ‘’}${days == 0 ? ‘’ : days += ‘天’}${hours == 0 ? ‘’ : hours += ‘小时’}${minutes == 0 ? ‘’ : minutes += ‘分钟’}${seconds == 0 ? ‘’ : seconds += ‘秒’}${millis == 0 ? ‘’ : millis += ‘毫秒’}${nanos == 0 ? ‘’ : nanos += ‘纳秒’}
@DurationMin 被注释的元素必须大于${inclusive == true ? ‘或等于’ : ‘’}${days == 0 ? ‘’ : days += ‘天’}${hours == 0 ? ‘’ : hours += ‘小时’}${minutes == 0 ? ‘’ : minutes += ‘分钟’}${seconds == 0 ? ‘’ : seconds += ‘秒’}${millis == 0 ? ‘’ : millis += ‘毫秒’}${nanos == 0 ? ‘’ : nanos += ‘纳秒’}

在Controller中校验数据

  1. @Slf4j
  2. @RestController
  3. @RequestMapping(value = "/index")
  4. public class IndexController {
  5. @RequestMapping(value = "/save", method = RequestMethod.POST)
  6. public R save(@Validated ValidationModel validationModel) {
  7. log.info("接收到参数为 [{}]", validationModel);
  8. return R.success(validationModel);
  9. }
  10. }

简单封装一个返回参数类

  1. @Data
  2. public class R<T> implements Serializable {
  3. private Integer code;
  4. private String message;
  5. private T data;
  6. public R() {
  7. this.code = 200;
  8. this.message = "SUCCESS";
  9. }
  10. public static <T> R<T> success(T data) {
  11. R r = new R();
  12. r.setData(data);
  13. return r;
  14. }
  15. public static R fail() {
  16. R r = new R();
  17. r.setCode(500);
  18. r.setMessage("FAIL");
  19. return r;
  20. }
  21. public static R fail(String message) {
  22. R r = new R();
  23. r.setCode(500);
  24. r.setMessage(message);
  25. return r;
  26. }
  27. }

postman

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NvdWwwOTI4_size_16_color_FFFFFF_t_70 1

相应参数

  1. {
  2. "timestamp": "2019-09-14T15:17:25.919+0000",
  3. "status": 400,
  4. "error": "Bad Request",
  5. "errors": [
  6. {
  7. "codes": [
  8. "Range.validationModel.age",
  9. "Range.age",
  10. "Range.java.lang.String",
  11. "Range"
  12. ],
  13. "arguments": [
  14. {
  15. "codes": [
  16. "validationModel.age",
  17. "age"
  18. ],
  19. "arguments": null,
  20. "defaultMessage": "age",
  21. "code": "age"
  22. },
  23. 60,
  24. 18
  25. ],
  26. "defaultMessage": "年龄必须在 18 至 60 之间",
  27. "objectName": "validationModel",
  28. "field": "age",
  29. "rejectedValue": "10",
  30. "bindingFailure": false,
  31. "code": "Range"
  32. }
  33. ],
  34. "message": "Validation failed for object='validationModel'. Error count: 1",
  35. "path": "/index/save"
  36. }

实现全局统一返回参数

Spring MVC 在 org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler类已经进行了异常拦截,我们只需要找到相应的处理方法,重写就可以了

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NvdWwwOTI4_size_16_color_FFFFFF_t_70 2

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NvdWwwOTI4_size_16_color_FFFFFF_t_70 3

重写ResponseEntityExceptionHandler类的handleBindException

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NvdWwwOTI4_size_16_color_FFFFFF_t_70 4

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NvdWwwOTI4_size_16_color_FFFFFF_t_70 5

  1. @Slf4j
  2. @RestControllerAdvice
  3. public class ValidationInterceptor extends ResponseEntityExceptionHandler {
  4. @Override
  5. public ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
  6. return new ResponseEntity<>(getError(ex.getBindingResult().getAllErrors()) , status);
  7. }
  8. /**
  9. * 解决 JSON 请求统一返回参数
  10. */
  11. @Override
  12. protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
  13. return new ResponseEntity<>(getError(ex.getBindingResult().getAllErrors()) , status);
  14. }
  15. private R getError(List<ObjectError> allErrors) {
  16. StringBuffer message = new StringBuffer();
  17. for(ObjectError error: allErrors){
  18. message.append(error.getDefaultMessage()).append(" & ");
  19. }
  20. log.error(message.substring(0, message.length() - 3)); // 因为&两边空格
  21. return R.fail(message.substring(0, message.length() - 3));
  22. }
  23. }

postman 再次请求,返回参数

  1. {
  2. "code": 500,
  3. "message": "年龄必须在 18 至 60 之间",
  4. "data": null
  5. }

自定义注解

  1. **虽然Bean ValidationHibernate Validator已经提供了非常丰富的校验注解,但是在实际业务中,难免会碰到一些现有注解不足以校验的情况;这时,我们可以考虑自定义Validation注解。**

创建自定义注解

  1. @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Constraint(validatedBy = {PhoneValidator.class}) // 指定此注解的实现,即:验证器
  5. public @interface PhoneValidatorAnnotation {
  6. boolean required() default true;
  7. String message() default "请输入正确的手机格式";
  8. Class<?>[] groups() default {};
  9. Class<? extends Payload>[] payload() default {};
  10. }

编写校验器 验证该注解

实现 ConstraintValidator 接口 并且重写 isValid 方法

  1. public class PhoneValidator implements ConstraintValidator<PhoneValidatorAnnotation, String> {
  2. private boolean required = false;
  3. @Override
  4. public void initialize(PhoneValidatorAnnotation phoneValidatorAnnotation) {
  5. required = phoneValidatorAnnotation.required();
  6. }
  7. @Override
  8. public boolean isValid(String value, ConstraintValidatorContext context) {
  9. return RegexUtils.regCheck(value, RegularConstants.PHONE_REGEXP);
  10. }
  11. }

正则常量

  1. public class RegularConstants {
  2. /**
  3. * 匹配电话
  4. */
  5. public static final Pattern PHONE_REGEXP = Pattern.compile("^1([38][0-9]|4[579]|5[0-3,5-9]|6[6]|7[0135678]|9[89])\\d{8}$");
  6. }

正则工具类

  1. public class RegexUtils {
  2. /**
  3. * @param str 被匹配的字符串
  4. * @param pattern 正则表达式
  5. * @return 是否匹配成功
  6. */
  7. public static boolean regCheck(String str, Pattern pattern){
  8. if (str == null || str.equals("")) {
  9. return false;
  10. }
  11. Matcher matcher = pattern.matcher(str);
  12. return matcher.matches();
  13. }
  14. }

使用自定义注解

  1. @Data
  2. public class ValidationModel {
  3. /**
  4. * 主键
  5. */
  6. @NotNull(message = "主键不能为空")
  7. private Long id;
  8. /**
  9. * 名称
  10. */
  11. @NotBlank(message = "名称不能为空")
  12. private String name;
  13. /**
  14. * 邮箱
  15. */
  16. @NotBlank(message = "email 不能为空")
  17. @Email(message = "email 格式不正确")
  18. private String email;
  19. /**
  20. * 年龄
  21. */
  22. @Range(min = 18, max = 60, message = "年龄必须在 {min} 至 {max} 之间")
  23. private String age;
  24. /**
  25. * 手机号
  26. */
  27. @PhoneValidatorAnnotation
  28. private String phone;
  29. }

postman测试

  1. {
  2. "code": 500,
  3. "message": "请输入正确的手机格式",
  4. "data": null
  5. }

分组校验

当使用Validation校验框架的时候,一般都会将校验信息配置在对于的参数类中,如上面的ValidationModel 实体类。这样一来,所有使用该参数类的Controller类对应的方法都要进行一次校验。

直接定义在参数类中的校验注解,需要满足当参数类被多个Controller所共用时,每个Controller方法对该参数类有不同的校验规则。

创建两个接口,不需要任何参数

  1. public interface AddGroups {
  2. }
  3. public interface DeleteGroups {
  4. }
  5. public interface UpdateGroups {
  6. }

参数类

  1. @Data
  2. public class ValidationModel {
  3. /**
  4. * 主键
  5. */
  6. @NotNull(message = "主键不能为空", groups = {AddGroups.class, UpdateGroups.class, DeleteGroups.class})
  7. private Long id;
  8. /**
  9. * 名称
  10. */
  11. @NotBlank(message = "名称不能为空", groups = {AddGroups.class, UpdateGroups.class})
  12. private String name;
  13. /**
  14. * 邮箱
  15. */
  16. @NotBlank(message = "email 不能为空", groups = {AddGroups.class, UpdateGroups.class})
  17. @Email(message = "email 格式不正确", groups = {AddGroups.class, UpdateGroups.class})
  18. private String email;
  19. /**
  20. * 年龄
  21. */
  22. @Range(min = 18, max = 60, message = "年龄必须在 {min} 至 {max} 之间", groups = {AddGroups.class, UpdateGroups.class})
  23. private String age;
  24. /**
  25. * 手机号
  26. */
  27. @PhoneValidatorAnnotation(groups = {AddGroups.class, UpdateGroups.class})
  28. private String phone;
  29. /**
  30. * 描述
  31. */
  32. @NotBlank(message = "描述不能为空")
  33. @Length(min = 10, max = 100, message = "描述长度必须在 {min} 至 {max} 之间")
  34. private String presentation;
  35. }

在Controller中校验数据,只需要在该实体类前面的@Validated注解中添加一个value值即可,该value值指定校验规则所在的接口

  1. @Slf4j
  2. @RestController
  3. @RequestMapping(value = "/index")
  4. public class IndexController {
  5. @RequestMapping(value = "/save", method = RequestMethod.POST)
  6. public R save(@Validated(AddGroups.class) ValidationModel validationModel) {
  7. log.info("save 接收到参数为 [{}]", validationModel);
  8. return R.success(validationModel);
  9. }
  10. @RequestMapping(value = "/delete", method = RequestMethod.DELETE)
  11. public R delete(@Validated(DeleteGroups.class) ValidationModel validationModel) {
  12. log.info("delete 接收到参数为 [{}]", validationModel);
  13. return R.success(validationModel);
  14. }
  15. @RequestMapping(value = "/update", method = RequestMethod.PUT)
  16. public R update(@Validated(UpdateGroups.class) ValidationModel validationModel) {
  17. log.info("delete 接收到参数为 [{}]", validationModel);
  18. return R.success(validationModel);
  19. }
  20. @RequestMapping(value = "/jsonSave", method = RequestMethod.POST)
  21. public R jsonSave(@Validated @RequestBody ValidationModel validationModel) {
  22. log.info("jsonSave 接收到参数为 [{}]", validationModel);
  23. return R.success(validationModel);
  24. }
  25. }

@Validated与@Valid的简单对比说明

@Valid注解与@Validated注解功能大部分类似;两者的不同主要在于:@Valid属于javax下的,而@Validated属于spring下;@Valid支持嵌套校验、而@Validated不支持,@Validated支持分组,而@Valid不支持。

@Validated分组**上面已经写过就不再多写,下面为@Valid**

@Valid

参数类 如果一个参数类中包含第二个bean,这时要检验第二个bean中某个字段,即嵌套校验,必须要在第一个参数类对象中使用@Valid标注到表示第二个bean对象的字段上,然后再第二个bean对象里面的字段上加上校验类型

  1. @Data
  2. public class ValidModel {
  3. @NotNull(message = "ValidModel主键不能为空")
  4. private Long id;
  5. @Valid
  6. private ValidBean validBean;
  7. @Data
  8. private class ValidBean {
  9. @NotNull(message = "ValidBean主键不能为空")
  10. private Long id;
  11. @NotBlank(message = "ValidBean名称不能为空")
  12. private String name;
  13. }
  14. }

在Controller中校验数据,只需要在该实体类前面加上@Valid即可​​​​​​​

  1. @Slf4j
  2. @RestController
  3. @RequestMapping(value = "/valid")
  4. public class ValidController {
  5. @RequestMapping(value = "/jsonSave", method = RequestMethod.POST)
  6. public R jsonSave(@Valid @RequestBody ValidModel validModel) {
  7. log.info("jsonSave 接收到参数为 [{}]", validModel);
  8. return R.success(validModel);
  9. }
  10. }

项目已上传至 GitHub https://github.com/soul0928/liuyun-validator , 不足之处望各位海涵

发表评论

表情:
评论列表 (有 0 条评论,660人围观)

还没有评论,来说两句吧...

相关阅读