validation校验API 参数 javax validator包支持通过注解自由地校验Java Bean的参数;通过切面并借由validator的能力拓展实现了对基本类型参数的校验。
3365
2017-09-04
maven
由于公司框架原因,使用的是1.1.0 Final版本,javax.validation目前最新的是2.0.0 Final,hibernate-validator也有6.0.*的版本了。
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.4.Final</version>
</dependency>
校验介绍
validation集成了Hibernate Validator和Spring Validator,通过对JSR303标准的拓展,实现了较为常用的参数校验。
不过业务上的需求以及项目开发的内部习惯往往更加多样和复杂,validation也提供了自定义校验类型的接口。并且我们往往不仅需要校验java bean类型的参数,还需要对路由参数和请求参数进行校验和处理,所以还需要拓展一下validation的能力。
实践
自定义校验类型
注解的定义
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;
import nd.huayu.leaf.validate.impl.EnumValidatorImpl;
import nd.huayu.leaf.validate.impl.UUIDValidatorImpl;
/**
* 自定义参数校验注解.
* 校验输入值是否在给定的枚举中.
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = EnumValidatorImpl.class) //// 注解的实现类为EnumValidatorImpl
public @interface Enum {
/**
* 是否必须.
* 为false时,则如果该字段有值才会校验,为空或者null时不校验.
*
* @return boolean boolean
*/
boolean required() default false;
/**
* value.
*
* @return string [ ]
*/
int[] value() default {};
/**
* 错误信息.
*
* @return the string
*/
String message() default "传递的值不在给定的范围内";
/**
* Groups class [ ].
*
* @return the class [ ]
*/
Class<?>[] groups() default {};
/**
* Payload class [ ].
*
* @return the class [ ]
*/
Class<? extends Payload>[] payload() default {};
/**
* 定义List,为了让Bean的一个属性上可以添加多套规则.
*/
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@interface List {
/**
* Value uuid [ ].
*
* @return the uuid [ ]
*/
Enum[] value();
}
}
注解的校验逻辑实现
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import nd.huayu.leaf.validate.Enum;
import org.apache.commons.lang3.StringUtils;
/**
* Description: Enum校验实现.
* Date: 2017/8/31 0031
* Time: 11:55
*/
public class EnumValidatorImpl implements ConstraintValidator<Enum, Integer> {
private boolean required;
private int[] values;
@Override
public void initialize(Enum constraintAnnotation) {
this.required = constraintAnnotation.required();
this.values = constraintAnnotation.value();
}
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
if (null == value) {// 非必填且为空值
return !required;
}
for (int value1 : values) {
if (value1 == value) {
return true;
}
}
return false;
}
}
实际使用
@RequestMapping(value = "/v1/list", method = RequestMethod.GET)
PageResult<TestVo> getListTest(@Enum(value = { 0, 1 }) @RequestParam(value = "testparam", required = true) Integer testparam)
这样,当访问/v1/test时,如果testparam不是0或者1,那么就会直接抛错。
拓展validation,支持基本参数校验
当然,如果仅在maven中引入了validation,那么在路由参数上写再多校验注解也是没用的,因为它只会校验java bean的参数,也就是被@RequestBody注解的参数。所以要校验基本类型参数,还需要额外的代码。
切面类的实现
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.validation.BeanPropertyBindingResult;
import org.springframework.validation.ObjectError;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableValidator;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Set;
/**
* Description: 参数校验切面.
* Date: 2017/8/31 0031
* Time: 15:30
*/
@Aspect
@Component
public class ParamValidAspect {
// log
protected final Log logger = LogFactory.getLog(getClass());
/**
* 切点.
*/
@Pointcut("(@within(org.springframework.web.bind.annotation.RestController)||@within(org.springframework.stereotype.Controller)) && execution(public * *(..))")
public void valid() {
// System.out.printf("------------");
}
/**
* 前置.
* @param pjp the pjp
*/
@Before("valid()")
public void before(JoinPoint pjp) {
try {
// 没参数,不校验
Object[] objects = pjp.getArgs();
if (objects.length == 0) {
return;
}
/**************************校验封装好的javabean**********************/
// 判断是否是基本类型
for (Object object : objects) {
if (object instanceof String || object instanceof Integer || object instanceof Double
|| object instanceof Long || object instanceof Boolean || object instanceof Float) {
continue;
}
Set<ConstraintViolation<Object>> beanValidResult = this.validMethodBeanParams(object);
if (!beanValidResult.isEmpty()) {
for (ConstraintViolation<Object> constraintViolation : beanValidResult) {
System.out.println(constraintViolation);
}
}
}
/**************************校验普通参数*************************/
// 获得切入目标对象
Object target = pjp.getThis();
// 获得切入的方法
Method method = ((MethodSignature) pjp.getSignature()).getMethod();
// 执行校验,获得校验结果
Set<ConstraintViolation<Object>> validResult = this.validMethodSimpleParams(target, method, objects);
// 如果有校验不通过的
if (!validResult.isEmpty()) {
// 获得方法的参数名称
String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
// 只返回第一个错误
ConstraintViolation<Object> constraintViolation = validResult.iterator().next();
// 获得校验的参数路径信息
PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
// 获得校验的参数位置
int paramIndex = pathImpl.getLeafNode().getParameterIndex();
// 获得校验的参数名称
String paramName = parameterNames[paramIndex];
// 校验信息
StringBuilder errMsg = new StringBuilder();
errMsg.append(SR.format(WafResource.VALIDATOR_EXCEPTION, paramName, constraintViolation.getMessage()));
throw new ArgumentValidationException(errMsg.toString());
}
} catch (Throwable e) {
this.logger.info(e.getMessage(), e);
throw new ArgumentValidationException(e.getMessage(), e);
}
}
private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
private final ExecutableValidator executableValidator = factory.getValidator().forExecutables();
private final Validator validator = factory.getValidator();
/**
* 基本类型数据手动校验;
* @param obj T
* @param method Method
* @param params Object[]
* @param <T> T
* @return T
*/
private <T> Set<ConstraintViolation<T>> validMethodSimpleParams(T obj, Method method, Object[] params) {
return executableValidator.validateParameters(obj, method, params);
}
/**
* java bean 手动校验;
* @param obj T
* @param <T> T
* @return T
*/
private <T> Set<ConstraintViolation<T>> validMethodBeanParams(T obj) {
return validator.validate(obj);
}
}
切面的使用不再赘述,之前的文章也简单介绍过如何定义切面。
代码基本都写了注释,其中最主要的部分其实就是两句executableValidator.validateParameters(obj, method, params);和validator.validate(obj);
这是借助了Validation的接口进行的手动校验,以此实现能够对路由基本类型参数校验的能力。