[TOC]
对于后端开发, 有很多表单数据需要被校验是否合理, 如果在代码中编写大量的if-else
会严重影响阅读体验, 所以需要寻找一个更好的参数校验方式来解决参数校验问题
@NotNull
, @NotEmpty
这些注解平时也经常使用, 这些也是经常见, 经常使用的
这些注解在javax提供的校验包中: javax.validation.constraints
由javax提供的校验注解中总共包含如下这些:
注解
作用数据类型
描述
@AssertFalse
布尔值
被校验的元素必须为False
@AssertTrue
布尔值
被校验的元素必须为True
@DecimalMax
浮点数
被校验的元素必须小于等于配置值
@DecimalMin
浮点数
被校验的元素必须大于等于配置值
@Digits
数值
被校验的元素整数位不能超过Integer配置值, 浮点位数不能超过fractional配置值
字符串
被校验元素符合邮箱规则
@Future
时间类型
被校验的元素必须是一个未来时间
@FutureOrPresent
时间类型
被校验的元素必须是当前或未来时间
@Max
整数类型
被校验的数值最大不能超过配置值
@Min
整数类型
被校验额数值最小不能超过配置值
@Negative
整数类型
被校验的值必须为负整数
@NegativeOrZero
整数类型
被校验的值必须小于等于0
@NotBlank
字符串
被校验的元素至少包含一个非空白的字符
@NotEmpty
字符串/数组/集合
被校验的元素不是空, 被校验的集合不是空
@NotNull
Object
被校验的对象不为null
@Null
Object
被校验的对象必须为null
@Past
时间类型
被校验的元素必须是一个过去的时间
@PastOrPresent
时间类型
被校验的元素必须是当前或过去的某个时间
@Pattern
字符串
被校验的元素必须符合正则校验规则
@Positive
整数类型
被校验的数值必须为正整数
@PositiveOrZero
整数类型
被校验的数值必须大于等于0
@Size
字符串/数组/集合
字符串长度必须满足规则, 数组/集合元素个数必须满足min, max的配置规则
org.hibernate.validator:hibernate-validator
除了Javax提供的校验API定义之外, Hibernate也提供了一些校验注解, 仅罗列一些常用的, 如下:
注解
作用数据类型
描述
@Length
字符串
被校验的字符串必须满足min, max配置
@Range
整数类型/字符串
被校验的元素必须在min, max范围内
@URL
字符串
被校验的元素必须满足URL配置规则
除了这些我也看到了一些别的校验注解, 虽然没有使用过, 不过应该会想去尝试尝试
以上应该就是常用的校验注解了
我经常使用的web框架是SpringBoot, 所以正常情况下, 这些注解都是可以直接使用, 并且Validator已经被正确配置上了. 我们只需要考虑如何正确编写校验配置
Query参数只的是跟随在URL后面的请求参数例如: http://hostname.com/search?q=balabala
其中q就是query请求参数
首先我们需要在Controller上添加注解 @Validated (org.springframework.validation.annotation.Validated)
有了这个注解Spring才能帮我们在调用Controller之前去完成参数的校验
Controller方法的定义:
public void getCaptcha(@NotEmpty(message = "设备ID不能为空") String deviceId,
HttpServletResponse response) throws IOException {
}
不出意外的话, 如果请求参数缺少就会抛出异常 ConstraintViolationException
记得做全局异常处理~取出异常消息进行返回
if (e instanceof ConstraintViolationException) {
Set<ConstraintViolation<?>> constraintViolations = ((ConstraintViolationException) e).getConstraintViolations();
Iterator<ConstraintViolation<?>> iterator = constraintViolations.iterator();
if (iterator.hasNext()) {
ConstraintViolation<?> next = iterator.next();
message = next.getMessage();
}
}
当参数比较多的时候, 我们通常会选择用JavaBean来接收参数, 这种情况下, 我们只需要在JavaBean的Field上编写相应的注解并完成一些简单的配置即可,
JavaBean Field (我用了lombok)
@NotEmpty(message = "用户名不能为空")
@Length(max = 50, message = "最大长度50个字符")
private String name;
@NotEmpty(message = "密码不能为空")
@Length(max = 30, message = "用户名密码错误")
private String pwd;
@NotEmpty(message = "设备ID不能为空")
private String deviceId;
@NotNull(message = "验证码不能为空")
@Length(min = 6, max = 6, message = "验证码错误")
private String captcha;
Contoller 方法定义
public @ResponseBody CommonResponse login(@Validated @RequestBody AdminLoginReqDTO adminLoginReqDTO) {
}
同样不出意外的话可能会遇到 MethodArgumentNotValidException
异常
全局异常处理
} else if (e instanceof MethodArgumentNotValidException) {
BindingResult br = ((MethodArgumentNotValidException) e).getBindingResult();
FieldError fieldError = br.getFieldError();
message = fieldError == null ? null : fieldError.getDefaultMessage();
}
上面的内容只能算一个基础使用
下面单独讲讲自定义校验注解的编写
注解的内容就是定义元素的校验规则, 以我自己的项目为例, 有些参数不是必填的, 但是如果填写了就需要校验, 所以我定义了一个注解
@Documented
@Constraint(validatedBy = { NotRequireValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Repeatable(NotRequire.List.class)
public @interface NotRequire {
/**
* 可用于字符串的校验, 也可用于数值范围的校验
* @return
*/
long min() default 0;
/**
* 可用于字符串的校验, 也可用于数值范围的校验
* @return
*/
long max() default Long.MAX_VALUE;
String message() default "{art.yitongxue.ytxinterface.common.valid.NonNullValid}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
/**
* Defines several {@code @NotEmpty} constraints on the same element.
*
* @see NotEmpty
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Documented
public @interface List {
NotRequire[] value();
}
}
这个注解基本上就是从其他注解上抄过来的, 但是还要完善几个地方
@Repeatable(NotRequire.List.class)
记得修改为你的注解类名@Constraint(validatedBy = { NotRequireValidator.class })
一定要指定你的校验类 (我之前就没指定, 导致我的注解一直不生效)校验类的作用就是去实现注解里面的校验内容
public class NotRequireValidator implements ConstraintValidator<NotRequire, Object> {
private long min;
private long max;
@Override
public void initialize(NotRequire constraintAnnotation) {
this.min = constraintAnnotation.min();
this.max = constraintAnnotation.max();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
if (null == value) {
return true;
}
if (value instanceof String) {
int length = ((String) value).length();
return min <= length && length <= max;
} else if (value instanceof Integer || value instanceof Long) {
long val = ((Number) value).longValue();
return min <= val && val <= max;
}
return false;
}
}
initialize
方法是用来初始化数据, 也就是存放注解里面的配置信息isValid
方法是真实用来进行校验逻辑的方法完成如上两件工作之后, 就可以像使用@NotEmpty
这样的注解一样使用自定义注解了
如果想看看Hibernate是如何完成校验逻辑的可以在IDEA中搜索 ConstraintHelper
这个类, 这个类的构造方法中记录了所有的校验器. 可以分类去查看.
MethodValidationInterceptor
Validated
注解ConstraintViolationException
异常源码如下:
/**
* An AOP Alliance {@link MethodInterceptor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
*
* <p>Applicable methods have JSR-303 constraint annotations on their parameters
* and/or on their return value (in the latter case specified at the method level,
* typically as inline annotation).
*
* <p>E.g.: {@code public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)}
*
* <p>Validation groups can be specified through Spring's {@link Validated} annotation
* at the type level of the containing target class, applying to all public service methods
* of that class. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1 provider.
*
* @author Juergen Hoeller
* @since 3.1
* @see MethodValidationPostProcessor
* @see javax.validation.executable.ExecutableValidator
*/
public class MethodValidationInterceptor implements MethodInterceptor {
private final Validator validator;
/**
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
*/
public MethodValidationInterceptor() {
this(Validation.buildDefaultValidatorFactory());
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory.
* @param validatorFactory the JSR-303 ValidatorFactory to use
*/
public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
this(validatorFactory.getValidator());
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 Validator.
* @param validator the JSR-303 Validator to use
*/
public MethodValidationInterceptor(Validator validator) {
this.validator = validator;
}
@Override
@SuppressWarnings("unchecked")
public Object invoke(MethodInvocation invocation) throws Throwable {
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
Class<?>[] groups = determineValidationGroups(invocation);
// Standard Bean Validation 1.1 API
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) {
// Probably a generic type mismatch between interface and impl as reported in SPR-12237 / HV-1011
// Let's try to find the bridged method on the implementation class...
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;
}
private boolean isFactoryBeanMetadataMethod(Method method) {
Class<?> clazz = method.getDeclaringClass();
// Call from interface-based proxy handle, allowing for an efficient check?
if (clazz.isInterface()) {
return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
!method.getName().equals("getObject"));
}
// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
Class<?> factoryBeanType = null;
if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = SmartFactoryBean.class;
}
else if (FactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = FactoryBean.class;
}
return (factoryBeanType != null && !method.getName().equals("getObject") &&
ClassUtils.hasMethod(factoryBeanType, method.getName(), method.getParameterTypes()));
}
/**
* Determine the validation groups to validate against for the given method invocation.
* <p>Default are the validation groups as specified in the {@link Validated} annotation
* on the containing target class of the method.
* @param invocation the current MethodInvocation
* @return the applicable validation groups as a Class array
*/
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Validated validatedAnn = AnnotationUtils.findAnnotation(invocation.getMethod(), Validated.class);
if (validatedAnn == null) {
validatedAnn = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class);
}
return (validatedAnn != null ? validatedAnn.value() : new Class<?>[0]);
}
}
原网址: 访问
创建于: 2021-08-24 20:45:15
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论