1.什么是Bean Validation?
Bean Validation 是一个运行时的数据验证框架,在验证之后验证的错误信息会被马上返回。java 在2009年的 JAVAEE 6 中发布了 JSR303以及javax下的validation包内容。这项工作的主要目标是为java应用程序开发人员提供 基于java对象的 约束(constraints)声明 和 对约束的验证工具(validator),以及约束元数据存储库和查询API。但是该内容并没有具体的实现, Hibernate-Validator框架 提供了 JSR 303 规范中所有内置 constraint 的实现,除此之外还有一些附加的 constraint。
常用的Validation注解
一些最常见的验证注解如下:
@NotNull: 标记字段不能为 null
@NotEmpty: 标记集合字段不为空(至少要有一个元素)
@NotBlank: 标记字段串字段不能是空字符串(即它必须至少有一个字符)
@Min / @Max: 标记数字类型字段必须大于/小于指定的值
@Pattern: 标记字符串字段必须匹配指定的正则表达式
@Email: 标记字符串字段必须是有效的电子邮件地址
2.代码工程
实验目标:校验controller传递的参数
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>springboot-demo</artifactId> <groupId>com.et</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>validtion</artifactId> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-autoconfigure</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency> </dependencies> </project>
controller
Spring 自动将传入的 JSON 映射到 Java 对象参数上。 现在,我们要校验传入的 Java 对象是否满足我们预先定义的约束条件。 为了校验传入 HTTP 请求的请求实体,我们在 REST 控制器中使用 @Valid 注解对请求实体进行标记:
package com.et.validation.controller; import cn.hutool.json.JSON; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.et.validation.entity.UserInfoReq; import com.fasterxml.jackson.databind.util.JSONPObject; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; @RestController public class HelloWorldController { @RequestMapping("/hello") public Map<String, Object> showHelloWorld(@RequestBody @Validated UserInfoReq req){ Map<String, Object> map = new HashMap<>(); map.put("msg", JSONUtil.toJsonStr(req)); return map; } }
enttiy
我们有一个 int类型 字段,它的值必须介于 1 和 10200之间,如@Min 和@Max 注解所定义的那样。 我们还有一个 String 类型字段,它必须是一个 IP 地址,正如@Pattern 注解中的正则表达式所定义的那样(正则表达式实际上仍然允许大于 255 的无效 IP 地址,但在我们创建自定义验证器时,我们将在本教程的后面修复这个BUG)。
package com.et.validation.entity; import com.et.validation.validate.IpAddress; import lombok.Data; import lombok.ToString; import javax.validation.constraints.*; @Data @ToString public class UserInfoReq { @NotNull(message = "id不能为null") private Long id; @NotBlank(message = "username不能为空") private String username; @NotNull(message = "age不能为null") @Min(value = 1, message = "年龄不符合要求") @Max(value = 200, message = "年龄不符合要求") private Integer age; @Email(message = "邮箱不符合规范") private String email; @IpAddress(message = "ip不符合规范") //@Pattern(regexp = "^[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}$") private String ip; }
自定义error捕获
当校验失败时,我们通常希望向客户端返回一条有意义的错误消息。 为了使客户端能够显示有用的错误消息,我们应该返回一个统一的数据结构,其中包含每个校验失败的错误消息。 我们在这里所做的只是从异常中读取有关校验失败信息并将它们转换到我们的 ValidationErrorResponse 数据结构中。 请注意@ControllerAdvice 注解,它使得上述类型异常的异常处理机制对所有Controller全局可用。
package com.et.validation.error; import org.springframework.http.HttpStatus; import org.springframework.validation.FieldError; 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.ResponseStatus; import javax.validation.ConstraintViolation; import javax.validation.ConstraintViolationException; @ControllerAdvice class ErrorHandlingControllerAdvice { @ExceptionHandler(ConstraintViolationException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody ValidationErrorResponse onConstraintValidationException( ConstraintViolationException e) { ValidationErrorResponse error = new ValidationErrorResponse(); for (ConstraintViolation violation : e.getConstraintViolations()) { error.getViolations().add( new Violation(violation.getPropertyPath().toString(), violation.getMessage())); } return error; } @ExceptionHandler(MethodArgumentNotValidException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) @ResponseBody ValidationErrorResponse onMethodArgumentNotValidException( MethodArgumentNotValidException e) { ValidationErrorResponse error = new ValidationErrorResponse(); for (FieldError fieldError : e.getBindingResult().getFieldErrors()) { error.getViolations().add( new Violation(fieldError.getField(), fieldError.getDefaultMessage())); } return error; } }
package com.et.validation.error; import lombok.Data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; @Data public class ValidationErrorResponse implements Serializable { private List<Violation> violations = new ArrayList<>(); }
package com.et.validation.error; import lombok.Data; import java.io.Serializable; @Data public class Violation implements Serializable { private final String fieldName; private final String message; public Violation(String fieldName, String message) { this.fieldName = fieldName; this.message = message; } }
自定义validator
如果官方提供可用的校验注解不能满足我们的需要,我们自己可以定义一个自定义校验器。 自定义校验注解需要包含以下要素:
参数message, 指定 ValidationMessages.properties 文件中的属性键,用于在校验失败时解析提示消息,
参数 groups, 允许定义在何种情况下触发此校验(稍后我们将讨分组校验)
参数payload, 允许定义要通过此校验传递的Payload(因为这是一个很少使用的功能,我们不会在本教程中介绍它)
一个 @Constraint 注解, 指定实现 ConstraintValidator 接口的校验逻辑类。
校验器的实现如下所示:
package com.et.validation.validate; 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.RetentionPolicy.RUNTIME; @Target({ FIELD }) @Retention(RUNTIME) @Constraint(validatedBy = IpAddressValidator.class) @Documented public @interface IpAddress { String message() default "{IpAddress.invalid}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; }
package com.et.validation.validate; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Matcher; import java.util.regex.Pattern; class IpAddressValidator implements ConstraintValidator<IpAddress, String> { @Override public boolean isValid(String value, ConstraintValidatorContext context) { Pattern pattern = Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$"); Matcher matcher = pattern.matcher(value); try { if (!matcher.matches()) { return false; } else { for (int i = 1; i <= 4; i++) { int octet = Integer.valueOf(matcher.group(i)); if (octet > 255) { return false; } } return true; } } catch (Exception e) { return false; } } }
以上只是一些关键代码,所有代码请参见下面代码仓库
代码仓库
https://github.com/Harries/springboot-demo
3.测试
启动spring boot应用
测试参数校验接口
访问http://127.0.0.1:8088/hello,返回结果如下
4.引用
https://reflectoring.io/bean-validation-with-spring-boot/