validation校验API 参数 javax validator包支持通过注解自由地校验Java Bean的参数;通过切面并借由validator的能力拓展实现了对基本类型参数的校验。

maven

由于公司框架原因,使用的是1.1.0 Final版本,javax.validation目前最新的是2.0.0 Final,hibernate-validator也有6.0.*的版本了。

  1. <!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
  2. <dependency>
  3. <groupId>javax.validation</groupId>
  4. <artifactId>validation-api</artifactId>
  5. <version>1.1.0.Final</version>
  6. </dependency>
  7. <!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
  8. <dependency>
  9. <groupId>org.hibernate</groupId>
  10. <artifactId>hibernate-validator</artifactId>
  11. <version>5.2.4.Final</version>
  12. </dependency>

校验介绍

validation集成了Hibernate Validator和Spring Validator,通过对JSR303标准的拓展,实现了较为常用的参数校验。
不过业务上的需求以及项目开发的内部习惯往往更加多样和复杂,validation也提供了自定义校验类型的接口。并且我们往往不仅需要校验java bean类型的参数,还需要对路由参数和请求参数进行校验和处理,所以还需要拓展一下validation的能力。

实践

自定义校验类型

注解的定义
  1. import static java.lang.annotation.ElementType.*;
  2. import static java.lang.annotation.RetentionPolicy.RUNTIME;
  3. import java.lang.annotation.Documented;
  4. import java.lang.annotation.ElementType;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8. import javax.validation.Constraint;
  9. import javax.validation.Payload;
  10. import nd.huayu.leaf.validate.impl.EnumValidatorImpl;
  11. import nd.huayu.leaf.validate.impl.UUIDValidatorImpl;
  12. /**
  13. * 自定义参数校验注解.
  14. * 校验输入值是否在给定的枚举中.
  15. */
  16. @Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER })
  17. @Retention(RetentionPolicy.RUNTIME)
  18. @Documented
  19. @Constraint(validatedBy = EnumValidatorImpl.class) //// 注解的实现类为EnumValidatorImpl
  20. public @interface Enum {
  21. /**
  22. * 是否必须.
  23. * 为false时,则如果该字段有值才会校验,为空或者null时不校验.
  24. *
  25. * @return boolean boolean
  26. */
  27. boolean required() default false;
  28. /**
  29. * value.
  30. *
  31. * @return string [ ]
  32. */
  33. int[] value() default {};
  34. /**
  35. * 错误信息.
  36. *
  37. * @return the string
  38. */
  39. String message() default "传递的值不在给定的范围内";
  40. /**
  41. * Groups class [ ].
  42. *
  43. * @return the class [ ]
  44. */
  45. Class<?>[] groups() default {};
  46. /**
  47. * Payload class [ ].
  48. *
  49. * @return the class [ ]
  50. */
  51. Class<? extends Payload>[] payload() default {};
  52. /**
  53. * 定义List,为了让Bean的一个属性上可以添加多套规则.
  54. */
  55. @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
  56. @Retention(RUNTIME)
  57. @Documented
  58. @interface List {
  59. /**
  60. * Value uuid [ ].
  61. *
  62. * @return the uuid [ ]
  63. */
  64. Enum[] value();
  65. }
  66. }
注解的校验逻辑实现
  1. import javax.validation.ConstraintValidator;
  2. import javax.validation.ConstraintValidatorContext;
  3. import nd.huayu.leaf.validate.Enum;
  4. import org.apache.commons.lang3.StringUtils;
  5. /**
  6. * Description: Enum校验实现.
  7. * Date: 2017/8/31 0031
  8. * Time: 11:55
  9. */
  10. public class EnumValidatorImpl implements ConstraintValidator<Enum, Integer> {
  11. private boolean required;
  12. private int[] values;
  13. @Override
  14. public void initialize(Enum constraintAnnotation) {
  15. this.required = constraintAnnotation.required();
  16. this.values = constraintAnnotation.value();
  17. }
  18. @Override
  19. public boolean isValid(Integer value, ConstraintValidatorContext context) {
  20. if (null == value) {// 非必填且为空值
  21. return !required;
  22. }
  23. for (int value1 : values) {
  24. if (value1 == value) {
  25. return true;
  26. }
  27. }
  28. return false;
  29. }
  30. }
实际使用
  1. @RequestMapping(value = "/v1/list", method = RequestMethod.GET)
  2. 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注解的参数。所以要校验基本类型参数,还需要额外的代码。

切面类的实现
  1. import org.apache.commons.logging.Log;
  2. import org.apache.commons.logging.LogFactory;
  3. import org.aspectj.lang.JoinPoint;
  4. import org.aspectj.lang.annotation.Aspect;
  5. import org.aspectj.lang.annotation.Before;
  6. import org.aspectj.lang.annotation.Pointcut;
  7. import org.aspectj.lang.reflect.MethodSignature;
  8. import org.hibernate.validator.internal.engine.path.PathImpl;
  9. import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
  10. import org.springframework.core.ParameterNameDiscoverer;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.validation.BeanPropertyBindingResult;
  13. import org.springframework.validation.ObjectError;
  14. import javax.validation.ConstraintViolation;
  15. import javax.validation.Validation;
  16. import javax.validation.Validator;
  17. import javax.validation.ValidatorFactory;
  18. import javax.validation.executable.ExecutableValidator;
  19. import java.lang.reflect.Method;
  20. import java.util.List;
  21. import java.util.Set;
  22. /**
  23. * Description: 参数校验切面.
  24. * Date: 2017/8/31 0031
  25. * Time: 15:30
  26. */
  27. @Aspect
  28. @Component
  29. public class ParamValidAspect {
  30. // log
  31. protected final Log logger = LogFactory.getLog(getClass());
  32. /**
  33. * 切点.
  34. */
  35. @Pointcut("(@within(org.springframework.web.bind.annotation.RestController)||@within(org.springframework.stereotype.Controller)) && execution(public * *(..))")
  36. public void valid() {
  37. // System.out.printf("------------");
  38. }
  39. /**
  40. * 前置.
  41. * @param pjp the pjp
  42. */
  43. @Before("valid()")
  44. public void before(JoinPoint pjp) {
  45. try {
  46. // 没参数,不校验
  47. Object[] objects = pjp.getArgs();
  48. if (objects.length == 0) {
  49. return;
  50. }
  51. /**************************校验封装好的javabean**********************/
  52. // 判断是否是基本类型
  53. for (Object object : objects) {
  54. if (object instanceof String || object instanceof Integer || object instanceof Double
  55. || object instanceof Long || object instanceof Boolean || object instanceof Float) {
  56. continue;
  57. }
  58. Set<ConstraintViolation<Object>> beanValidResult = this.validMethodBeanParams(object);
  59. if (!beanValidResult.isEmpty()) {
  60. for (ConstraintViolation<Object> constraintViolation : beanValidResult) {
  61. System.out.println(constraintViolation);
  62. }
  63. }
  64. }
  65. /**************************校验普通参数*************************/
  66. // 获得切入目标对象
  67. Object target = pjp.getThis();
  68. // 获得切入的方法
  69. Method method = ((MethodSignature) pjp.getSignature()).getMethod();
  70. // 执行校验,获得校验结果
  71. Set<ConstraintViolation<Object>> validResult = this.validMethodSimpleParams(target, method, objects);
  72. // 如果有校验不通过的
  73. if (!validResult.isEmpty()) {
  74. // 获得方法的参数名称
  75. String[] parameterNames = parameterNameDiscoverer.getParameterNames(method);
  76. // 只返回第一个错误
  77. ConstraintViolation<Object> constraintViolation = validResult.iterator().next();
  78. // 获得校验的参数路径信息
  79. PathImpl pathImpl = (PathImpl) constraintViolation.getPropertyPath();
  80. // 获得校验的参数位置
  81. int paramIndex = pathImpl.getLeafNode().getParameterIndex();
  82. // 获得校验的参数名称
  83. String paramName = parameterNames[paramIndex];
  84. // 校验信息
  85. StringBuilder errMsg = new StringBuilder();
  86. errMsg.append(SR.format(WafResource.VALIDATOR_EXCEPTION, paramName, constraintViolation.getMessage()));
  87. throw new ArgumentValidationException(errMsg.toString());
  88. }
  89. } catch (Throwable e) {
  90. this.logger.info(e.getMessage(), e);
  91. throw new ArgumentValidationException(e.getMessage(), e);
  92. }
  93. }
  94. private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer();
  95. private final ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
  96. private final ExecutableValidator executableValidator = factory.getValidator().forExecutables();
  97. private final Validator validator = factory.getValidator();
  98. /**
  99. * 基本类型数据手动校验;
  100. * @param obj T
  101. * @param method Method
  102. * @param params Object[]
  103. * @param <T> T
  104. * @return T
  105. */
  106. private <T> Set<ConstraintViolation<T>> validMethodSimpleParams(T obj, Method method, Object[] params) {
  107. return executableValidator.validateParameters(obj, method, params);
  108. }
  109. /**
  110. * java bean 手动校验;
  111. * @param obj T
  112. * @param <T> T
  113. * @return T
  114. */
  115. private <T> Set<ConstraintViolation<T>> validMethodBeanParams(T obj) {
  116. return validator.validate(obj);
  117. }
  118. }

切面的使用不再赘述,之前的文章也简单介绍过如何定义切面。
代码基本都写了注释,其中最主要的部分其实就是两句executableValidator.validateParameters(obj, method, params);validator.validate(obj);
这是借助了Validation的接口进行的手动校验,以此实现能够对路由基本类型参数校验的能力。