参数校验工具之Validator自定义校验

2019-03-05  本文已影响0人  从入门到脱发

validator百度博客一大堆,本文是摘取的部分内容结合在项目中使用的经验.

一.导入maven依赖

    <dependency> 
      <groupId>javax.validation</groupId>  
      <artifactId>validation-api</artifactId>  
      <version>1.1.0.Final</version> 
    </dependency>  
    <dependency> 
      <groupId>org.hibernate</groupId>  
      <artifactId>hibernate-validator</artifactId>  
      <version>5.2.0.Final</version> 
    </dependency> 

二.给需要校验的pojo加上注解

@Data
public class InformationBasicInfoAO {
   /**
    * 主键id
    */
   private Long id;
   /**
    * 文章标题
    */
   @NotBlank(message = "文章标题不能为空")
   private String title;
   /**
    * 发布日期
    */
   @NotNull(message = "发布时间不能为空")
   private Date releaseDate;
   /**
    * 文章来源
    */
   @NotBlank(message = "文章来源不能为空")
   private String source;

message的内容是返回给前端的提醒信息,可以不填

三.校验

1.通用的校验工具
@Slf4j
public class ValidatorUtil {
    //也可以使用spring注入的方式
    private static Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
    /**
     * 使用指定分组
     *
     * @param object 被校验的bean
     * @param groups 分组
     * @return
     */
    public static <T> Map<String, StringBuilder> validate(T object, Class<?>... groups) {
        Map<String, StringBuilder> errorMap = new HashMap<>(16);
        if (groups == null) {
            groups = new Class[]{Default.class};
        }
        Set<ConstraintViolation<T>> set = validator.validate(object, groups);
        if (CollectionUtils.isEmpty(set)) {
            return null;
        }
        String property;
        for (ConstraintViolation<T> c : set) {
            // 这里循环获取错误信息,可以自定义格式
            property = c.getPropertyPath().toString();
            if (errorMap.get(property) != null) {
                errorMap.get(property).append(",").append(c.getMessage());
            } else {
                StringBuilder sb = new StringBuilder();
                sb.append(c.getMessage());
                errorMap.put(property, sb);
            }
        }
        return errorMap;
    }
}
2.使用工具校验

这种方式直接抛去异常,全局异常处理

        Map<String, StringBuilder> errorMap = ValidatorUtil.validate(object);
        if (!CollectionUtils.isEmpty(errorMap)) {
            log.info("------参数校验失败:{}", errorMap);
            throw new ApplicationException(new ExceptionContext("参数校验失败"), "000005", errorMap.toString());
        }

也可以在controller中返回.

      Map<String, StringBuilder> errorMap = ValidatorUtils.validate(accountAO);
      if (!CollectionUtils.isEmpty(errorMap)) {
          simpleData.setData(errorMap);
          result.setContent(simpleData);
          return result;
      }

也可以在controller的形参上使用注解@Valid,这种方式不需要util类,可以直接使用,直接抛出异常,需要加配置项,具体可以百度,个人不喜欢这种方式,不够灵活.

    @PostMapping(value = "/order")
    public PojoResult<String> purchase(@RequestBody @Valid OrderAO orderAO,
                                       HttpServletRequest request) throws IOException, ServletException {
        PojoResult<String> pojoResult = new PojoResult<>();
}

三.自定义注解校验

框架自带的注解只能解决一些常规的校验,一些复杂逻辑的校验,可以使用自定义校验器,这是本人很喜欢的功能,重复的复杂的校验逻辑可以使用注解很优雅的代替,让代码的耦合性降低,也更简洁.
自定义校验的基本思路是--自定义注解+自定义校验器

自定义注解
//这里可以加上原生的注解,直接使用非空等功能
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
//这里需要引入校验器
@Constraint(validatedBy = CheckTagsValidator.class)
@Documented
public @interface CheckTags {
    String message() default "";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}
自定义校验器
 //接口的两个泛型,分别是自定义注解和注解校验的参数类型
public class CheckTagsValidator implements ConstraintValidator<CheckTags, List<String>> {
    @Override
    public void initialize(CheckTags constraintAnnotation) {
   }
     //方法体内可以写自己需要校验的逻辑,返回值为false则校验不通过
     @Override
     public boolean isValid(List<String> value, ConstraintValidatorContext context) {
        boolean isValid = false;
        //限定最多3个tag,最少可以0个,tag不超过4个汉字
        if (CollectionUtils.isEmpty(value)) {
            return true;
        }
        String filter = value.stream().filter(tag -> tag.length() > 4).findAny().orElse(null);
        if (StringUtils.isBlank(filter) && value.size() < 4) {
            isValid = true;
        }
        if (!isValid) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate("标签输入错误").addConstraintViolation();
        }
        return isValid;
    }
}

结合ValidatorUtil使用

四.Controller中校验参数

Validator本身是支持用@Valid注解自动校验的,也可可以配置校验返回策略,如下

@Configuration
public class ValidatorConfiguration {
    @Bean
    public Validator validator(){
        ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class )
                .configure()
                .addProperty( "hibernate.validator.fail_fast", "true" )
                .buildValidatorFactory();
        Validator validator = validatorFactory.getValidator();
        return validator;
    }
}

个人并不推荐这种方式,参数不对时会直接抛出异常,然后@ControllerAdvice全局异常处理,返回结果
有两个弊端,一是try/catch相对而言性能较低,二是返回异常结果不够清晰;
推荐使用ValidatorUtil结合AOP,直接返回结果,代码如下

@Slf4j
@Aspect
@Component
public class ValidatorAspect {

    private static final String METHOD_POST = "POST";

    @Around("execution(* com.test.government.affair.controller..*(..))")
    public Object handlerControllerMethod(ProceedingJoinPoint point) throws Throwable {
        long s = System.currentTimeMillis();
        // 从获取RequestAttributes中获取HttpServletRequest的信息
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) requestAttributes
                .resolveReference(RequestAttributes.REFERENCE_REQUEST);
        BucSSOUser user = SimpleUserUtil.getBucSSOUser(request);
        Object[] args = point.getArgs();
        //校验post请求参数
        log.info("Controller: 请求 URI( {} )|   user( {} )|   args:( {} )|  ", request.getRequestURI(), user.getEmpId(), args);
        if (METHOD_POST.equals(request.getMethod())) {
            for (int i = 0; i < args.length; i++) {
                Object arg = args[i];
                Map<String, StringBuilder> errorMap = ValidatorUtil.validate(arg);
                if (!CollectionUtils.isEmpty(errorMap)) {
                    log.info("参数校验失败  errorMap:{}", errorMap);
                    return ServiceResult.error("000005", errorMap.toString());
                }
            }
        }
        Object proceed = point.proceed();
        log.info("Controller: 响应 result:( {} )|   请求耗时:( {} )", proceed, System.currentTimeMillis() - s);
        return proceed;
    }
}

统一参数校验,同时打印log

上一篇下一篇

猜你喜欢

热点阅读