文章目录
- 参数校验
- - Validation
- - Hibernate Validator
- 校验注解
- 注解通用属性
- - Validator 内置注解
- - Hibernate Validator 附加注解
- 校验开启
- @Valid和@Validated
- 校验分类
- - Controller校验
- - Spring Bean校验
- 校验使用
- - Controller:参数对象校验
- - Spring Bean:方法返回值对象校验
- - Spring Bean:方法参数对象校验
- 分组校验
- - Controller:方法参数分组校验
- 嵌套校验
- - Controller:方法参数嵌套校验
- - Spring Bean:方法返回值嵌套校验
- - Spring Bean:方法参数嵌套校验
- 自定义注解
参数校验
Spring Boot框架默认使用Hibernate Validator作为校验器实现。参数校验使用JSR-303规范定义的校验注解来实现的。
实际上,在Spring项目中,参数校验并仅支持用在Controller层上面,其实是支持用在所有的Spring Bean上(Controller、Service、Repository等)。
配置依赖:
-
SpringMVC项目:
<dependency> <groupId></groupId> <artifactId>hibernate-validator</artifactId> <version>6.0.</version> </dependency>
-
SpringBoot项目:
<dependency> <groupId></groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
- Validation
并不是 JDK 的一部分,它是 Java EE 的一部分,它提供了基于注释和 API 的对象校验功能,即 JSR 303 规范。JSR 303 规范定义了一套用于在 JavaBean 中定义验证约束的元数据模型并提供用于编程方式和声明式验证的 API。
- 在 JavaEE6 中,这个规范被定义为 JSR 303
- 在 JavaEE7 中则成为了 JSR 349
- 在 JavaEE8 中成为 JSR 380
包中包含了很多注解,如@NotNull、@Size、@Pattern,它们可以用来对 JavaBean 中的属性进行各种验证,以保证数据的有效性和安全性。
需要注意的是,虽然 JSR-303 规范是 Java EE 的一部分,但我们仍然可以在非 Java EE 的应用程序中使用它,例如 Spring Boot、Spring MVC 等。在使用时需要导入 的 jar 包或者其他支持JSR 303规范的第三方库。
- Hibernate Validator
hibernate-validator是一个流行的Java数据校验器库,它基于JSR 303规范实现了Bean Validation 2.0 API,并提供了许多常用的校验注解和校验器。
校验注解
注解通用属性
-
message:错误提示信息
可以使用 {} 占位符来引用注解中的属性值或自定义的文本。可以在错误消息中动态地插入变量的值,使错误消息更具体和有意义。
使用方式:
- 自定义文本
@Size(min = 5, max = 10, message = "用户名长度不正确") private String username;
- 引用注解中的属性值
@Size(min = 5, max = 10, message = "用户名长度必须介于 {min} 到 {max} 之间") private String username;
- 引用配置文件配置参数的值
为了提供国际化和本地化支持,错误消息通常会使用特定的键(key)来标识。这些键将在校验过程中与消息资源包(message bundle)中的实际错误消息文本进行映射。@Size(min = 5, max = 10, message = "{}") private String username;
- 自定义文本
-
groups:所属的校验组
校验组是一种逻辑分组,可以根据不同的场景或需求对校验规则进行分组。默认情况下,参数校验不属于任何校验组,即属于默认组(Default)。
-
payload:携带额外的信息负载
这些信息负载可以在校验失败时获得。通常情况下,我们不需要使用该属性。
- Validator 内置注解
注解 | 注解的其它属性 | 注解描述 |
---|---|---|
@Null | 验证对象是否为 null | |
@NotNull | 验证对象不为 null | |
@NotBlank | 验证字符串不为空,去除前后空格后长度大于 0 | |
@NotEmpty | 验证对象不为 null 且不为空(如字符串、集合、数组等),去除前后空格后长度大于 0 | |
@Size | min:最小值 max:最大值 |
验证对象(字符串、集合、数组等)长度在 min 和 max 之间 |
@Max | value:最大值 | 验证数值(byte、short、int、long 等)是否小于等于指定的最大值 |
@Min | value:最小值 | 验证数值(byte、short、int、long 等)是否大于等于指定的最小值 |
@DecimalMax | value:最大值 inclusive:是否包含边界 |
验证 BigDecimal 和 BigInteger 的值是否小于等于指定的最大值 |
@DecimalMin | value:最小值 inclusive:是否包含边界 |
验证 BigDecimal 和 BigInteger 的值是否大于等于指定的最小值 |
@Digits | integer:整数位数 fraction:小数位数 |
验证数值是否符合整数位数和小数位数要求 |
@Pattern | regexp:正则表达式 | 验证字符串是否符合指定的正则表达式 |
验证字符串是否符合 Email 格式 | ||
@Future | 验证日期是否为将来某个时间 | |
@FutureOrPresent | 验证日期是否为将来某个时间或当前时间 | |
@Past | 验证日期是否为过去某个时间 | |
@PastOrPresent | 验证日期是否为过去某个时间或当前时间 | |
@AssertTrue | 验证 boolean 类型是否为 true | |
@AssertFalse | 验证 boolean 类型是否为 false | |
@Positive | 验证数值是否为正数 | |
@PositiveOrZero | 验证数值是否为正数或零 | |
@Negative | 验证数值是否为负数 | |
@NegativeOrZero | 验证数值是否为负数或零 |
@Max(value)、@Min(value)、@DecimalMax(value)、@DecimalMin(Value)区别:
- @Max、@Min接受一个Long类型的值
- @DecimalMax、@DecimalMin接受一个字符串类型的值(BigDecimal的字符串表示形式,因此可以是小数)
- 数字超过Long.MAX_VALUE或Long.MIN_VALUE以下或者数字是小数,@DecimalMax、@DecimalMin是唯一的选择。
- Hibernate Validator 附加注解
注解 | 注解的其它属性 | 注解描述 |
---|---|---|
@Length | min:最小长度 max:最大长度 |
验证字符串的长度是否在指定范围内 |
@Range | min:最小值 max:最大值 |
验证数字(BigDecimal, BigInteger, String, byte, short, int, long和原始类型的包装类 )是否在指定范围内 |
@UniqueElements | 验证集合中的元素是否唯一 | |
@URL | protocol:协议 host:主机 port:端口 regexp:正则表达式 flags:标志 |
验证字符串是否符合 URL 格式要求 |
@Hexadecimal | 验证字符串是否是十六进制的格式 | |
@ISBN | type:ISBN类型 | 验证字符串是否是有效的ISBN码 |
@CreditCardNumber | ignoreNonDigitCharacters:是否忽略非数字字符 | 验证字符串是否是有效的信用卡号码 |
@Currency | value:货币代码 | 验证字符串是否是有效的货币代码 |
@CodePointLength | min:最小码点长度 max:最大码点长度 |
验证字符串的码点长度是否在指定范围内 |
@Mod10Check | 验证字符串是否通过Mod 10算法校验 | |
@Mod11Check | 验证字符串是否通过Mod 11算法校验 | |
@LuhnCheck | 验证字符串是否通过Luhn算法校验 | |
@EAN | type:EAN 类型 | 验证字符串是否是有效的 EAN(欧洲文章编号)码 |
@ScriptAssert | lang:脚本语言 script:脚本内容 alias:脚本别名 reportOn:指定将错误报告应用于哪些属性 |
验证对象的属性是否满足指定的脚本表达式 |
@ParameterScriptAssert | lang:脚本语言 script:脚本内容 |
验证对象的属性是否满足指定的脚本表达式,可以在脚本中引用方法参数 |
校验开启
@Valid和@Validated是用于Spring框架中数据校验的注解。
@Valid和@Validated
对比 | @Valid | @Validated |
---|---|---|
校验规则 | JavaEE JSR-303(Bean Validation)规范定义的 | Spring定义的,对@Valid进行了二次封装。 所以既支持使用JSR-303规范的注解,也可以使用Spring提供的额外校验规则 |
使用范围 | 用在方法、参数、字段上 | 用在类、参数上 |
分组校验 | 不支持 | 支持 |
嵌套校验 | 支持 需要在嵌套的字段上面也加上@Valid |
不支持 |
异常处理 | 校验失败时抛出异常MethodArgumentNotValidException | 校验失败时抛出异常ConstraintViolationException |
校验分类
Spring支持校验范围可分为以下两类:
- Controller校验
- Spring Bean校验(包括Controller)
- Controller校验
Spring对Controller层的参数校验进行了专门处理,当请求到达Controller层前会先找到匹配的方法参数解析器(HandlerMethodArgumentResolver)进行参数解析,参数解析的过程中包含参数校验。
HandlerMethodArgumentResolver分析可参考:/JokerLJG/article/details/134754757
源码分析:
HandlerMethodArgumentResolver接口:
public interface HandlerMethodArgumentResolver {
// 判断该解析器是否支持解析给定的方法参数
boolean supportsParameter(MethodParameter parameter);
// 解析给定的方法参数,并返回解析后的参数值
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
下面以HandlerMethodArgumentResolver的其中一个实现类来分析。
RequestResponseBodyMethodProcessor类:
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
if (arg != null) {
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}
}
if (mavContainer != null) {
mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
// 遍历方法参数上的注解
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation ann : annotations) {
// 如果注解是Validated或注解以Valid开头时,进行参数校验
Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class);
if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann));
Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
binder.validate(validationHints);
break;
}
}
}
- 获取方法参数上的所有注解,遍历注解
- 当注解是Validated或注解以Valid开头时,进行参数校验
- Spring Bean校验
Spring Bean校验底层是通过AOP的方式实现的,通过MethodValidationPostProcessor动态注册AOP切面(以Validated注解为切点),然后使用MethodValidationInterceptor对切点方法织入增强。
注意:Spring Bean校验对Controller及其他Spring Bean都适用。
MethodValidationPostProcessor类:
public void afterPropertiesSet() {
// 为所有`@Validated`标注的Bean创建切面
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
// 创建Advisor进行增强
this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
}
// 创建Advice,本质就是一个方法拦截器(拦截Bean的所有方法)
protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
}
MethodValidationInterceptor类:
@Override
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable {
// 无需增强的方法,直接跳过
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
// 获取校验分组信息
Class<?>[] groups = determineValidationGroups(invocation);
ExecutableValidator execVal = this.validator.forExecutables();
Method methodToValidate = invocation.getMethod();
Set<ConstraintViolation<Object>> result;
// 方法参数校验
try {
result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
} catch (IllegalArgumentException ex) {
methodToValidate = BridgeMethodResolver.findBridgedMethod(ClassUtils.getMostSpecificMethod(invocation.getMethod(), invocation.getThis().getClass()));
result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);
}
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
Object returnValue = invocation.proceed();
// 方法返回值校验
result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);
if (!result.isEmpty()) {
throw new ConstraintViolationException(result);
}
return returnValue;
}
- 方法参数校验:当方法参数上有@Valid或参数校验注解(@NotNull、@NotBlank、@Length等)时,会进行校验。
- 方法返回值校验:当方法参数上有@Valid或参数校验注解(@NotNull、@NotBlank、@Length等)时,会进行校验。
校验使用
校验分类 | 校验范围 | |
---|---|---|
Controller | 方法参数对象校验 | Controller适用 |
Spring Bean | 方法返回值对象校验 | Controller及其他Spring Bean都适用 |
Spring Bean | 方法参数对象校验 | Controller及其他Spring Bean都适用 |
注意:下面的示例中,项目做了全局异常处理。
- Controller:参数对象校验
对Controller方法参数对象进行校验。
@Data
public class User {
@NotNull(message = "userId不能为空")
public String userId;
}
-
使用@Valid+校验注解:
方法参数上加@Valid,参数对象中使用校验注解
代码
@RestController @RequestMapping("valid") public class ValidController { @PostMapping("test11") public User test11(@RequestBody @Valid User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test11' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "userId不能为空", "data": null }
-
使用@Validated+校验注解:
方法参数上加@Validated,参数对象中使用校验注解
代码
@RestController @RequestMapping("valid") public class ValidController { @PostMapping("test12") public User test12(@RequestBody @Validated User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test12' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "userId不能为空", "data": null }
- Spring Bean:方法返回值对象校验
对Spring Bean(包括Controller)方法返回值对象进行校验。
注意:方法返回值对象校验时,注解放到方法前面或整个方法上面都是相同的效果。
@Data
public class User {
@NotNull(message = "userId不能为空")
public String userId;
}
-
使用@Validated+@Valid+校验注解
Spring Bean类上加@Validated,方法返回值上加@Valid,返回值对象中使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @Autowired private ValidService validService; @PostMapping("test13") public User test13(@RequestBody User user) { return validService.test13(user); } } @Validated @Service public class ValidService { public @Valid User test13(User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test13' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "userId不能为空", "data": null }
-
使用@Validated+校验注解
Spring Bean类上加@Validated,方法返回值上直接使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @Autowired private ValidService validService; @PostMapping("test14") public @NotNull User test14(@RequestBody User user) { return validService.test14(user); } } @Validated @Service public class ValidService { public @NotNull User test14(User user) { return null; } }
请求
curl 'http://127.0.0.1:8888/valid/test14' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "不能为null", "data": null }
- Spring Bean:方法参数对象校验
对Spring Bean(包括Controller)方法参数对象进行校验。
@Data
public class User {
@NotNull(message = "userId不能为空")
public String userId;
}
-
使用@Validated+@Valid+校验注解
Spring Bean类上加@Validated,方法参数上加@Valid,参数对象中使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @Autowired private ValidService validService; @PostMapping("test15") public User test15(@RequestBody User user) { return validService.test15(user); } } @Validated @Service public class ValidService { public User test15(@Valid User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test15' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "userId不能为空", "data": null }
-
使用@Validated+校验注解
Spring Bean类上加@Validated,方法参数上直接中使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @Autowired private ValidService validService; @PostMapping("test16") public User test16(@RequestBody User user) { return validService.test16(null); } } @Validated @Service public class ValidService { public User test16(@NotNull User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test16' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "不能为null", "data": null }
分组校验
@Data
public class User {
@NotNull(message = "userId不能为空")
public String userId;
@NotNull(message = "userName不能为空", groups = {AddGroup.class})
public String userName;
@NotNull(message = "age不能为空", groups = {UpdateGroup.class})
public String age;
}
- Controller:方法参数分组校验
对Controller方法参数对象进行分组校验。
-
使用@Validated+@Valid+校验注解
方法参数上加@Validated,并指定分组value属性,参数对象中的字段加校验注解并指定分组groups属性。
代码
@RestController @RequestMapping("valid") public class ValidController { @PostMapping("test17") public User test17(@RequestBody @Validated({AddGroup.class}) User user) { return user; } @PostMapping("test18") public User test18(@RequestBody @Validated({UpdateGroup.class}) User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test17' -H 'Content-Type: application/json' -d '{}' curl 'http://127.0.0.1:8888/valid/test18' -H 'Content-Type: application/json' -d '{}'
结果
{ "code": 501, "msg": "userName不能为空", "data": null } { "code": 501, "msg": "age不能为空", "data": null }
嵌套校验
@Data
public class User {
@NotNull(message = "userId不能为空")
public String userId;
@Valid
public Sex sex;
}
@Data
public class Sex {
@NotNull(message = "sexName不能为空")
public String sexName;
}
- Controller:方法参数嵌套校验
对Controller方法参数对象进行嵌套校验。
-
使用@Validated+@Valid+校验注解
方法参数上加@Validated,参数对象中的字段加@Valid,字段对象内再使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @PostMapping("test19") public User test19(@RequestBody @Validated User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test19' -H 'Content-Type: application/json' -d '{ "userId": "userId_a7699554a547", "sex": {} }'
结果
{ "code": 501, "msg": "sexName不能为空", "data": null }
-
使用@Valid+@Valid+校验注解
方法参数上加@Valid,参数对象中的字段加@Valid,字段对象内再使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @PostMapping("test20") public User test20(@RequestBody @Valid User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test20' -H 'Content-Type: application/json' -d '{ "userId": "userId_a7699554a547", "sex": {} }'
结果
{ "code": 501, "msg": "sexName不能为空", "data": null }
- Spring Bean:方法返回值嵌套校验
对Spring Bean(包括Controller)方法返回值对象进行嵌套校验。
-
使用@Validated+@Valid+@Valid+校验注解
Spring Bean类上加@Validated,方法返回值上加@Valid,返回值对象中字段加@Valid,字段内使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @Autowired private ValidService validService; @PostMapping("test21") public User test21(@RequestBody User user) { return validService.test21(user); } } @Validated @Service public class ValidService { public @Valid User test21(User user) { return user; } }
请求
curl 'http://127.0.0.1:8888/valid/test21' -H 'Content-Type: application/json' -d '{ "userId": "userId_a7699554a547", "sex": {} }'
结果
{ "code": 501, "msg": "sexName不能为空", "data": null }
- Spring Bean:方法参数嵌套校验
对Spring Bean(包括Controller)方法参数对象进行嵌套校验。
-
使用@Validated+@Valid+@Valid+校验注解
Spring Bean类上加@Validated,方法参数上加@Valid,参数对象中字段加@Valid,字段内使用校验注解。
代码
@RestController @RequestMapping("valid") public class ValidController { @Autowired private ValidService validService; @PostMapping("test22") public User test22(@RequestBody User user) { return validService.test22(user); } } @Validated @Service public class ValidService { public User test22(@Valid User user) { return user; } }
curl 'http://127.0.0.1:8888/valid/test22' -H 'Content-Type: application/json' -d '{ "userId": "userId_a7699554a547", "sex": {} }'
结果
{ "code": 501, "msg": "sexName不能为空", "data": null }
自定义注解
自定义参数校验工具的步骤:
- 自定义注解
- 自定义Validator
1. 自定义注解
定义一个IPV4地址的校验注解。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Target({ METHOD, FIELD, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(Ipv4.List.class)
@Constraint(validatedBy = {Ipv4Validator.class})
public @interface Ipv4 {
String message() default "not is Ipv4";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
@Target({ METHOD, FIELD, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
@interface List {
Ipv4[] value();
}
}
2. 自定义Validator
定义一个校验器,用于验证是否为IPV4地址。
import cn.hutool.core.lang.Validator;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class Ipv4Validator implements ConstraintValidator<Ipv4, String> {
@Override
public void initialize(Ipv4 constraintAnnotation) {
ConstraintValidator.super.initialize(constraintAnnotation);
}
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
// 对象为空验证通过
if (value == null) {
return true;
}
return Validator.isIpv4(value);
}
}
使用示例:
代码
@Validated
@RestController
@RequestMapping("valid")
public class ValidController {
@GetMapping("test23")
public String test23(@Ipv4(message = "ip不是IPV4地址") String ip) {
return ip;
}
}
请求
curl 'http://127.0.0.1:8888/valid/test23?ip=1.1.1'
结果
{
"code": 501,
"msg": "ip不是IPV4地址",
"data": null
}