stick-i / spel-validator

一个强大的 Java 参数校验包,基于 SpEL 实现,扩展自 jakarta.validation-api 包,用于简化参数校验,几乎支持所有场景下的参数校验。
https://spel-validator.sticki.cn/
Apache License 2.0
144 stars 33 forks source link

完成优化的todo:添加缓存、业务异常的处理 #19

Closed stick-i closed 2 months ago

stick-i commented 2 months ago
  1. 添加缓存和调整验证的逻辑顺序,以降低耗时。
  2. 提供一种方案,在执行spel的过程中,允许开发者自行处理业务中抛出的异常。由于spel允许开发者调用其他类的方法,所以很可能会出现一些业务异常(BusinessServiceException)被抛出,这些异常应该继续交给开发者,让开发者自行处理。
stick-i commented 2 months ago

由于校验过程中抛出的任何异常,都会被更上层的校验器(hibernate-validator)所捕获,并且转换成 javax.validation.ValidationException 再继续抛出。

比如这样的场景:

    public static ExampleEnum getByCode(Integer code) {
        if (code == null) {
            throw new IllegalArgumentException("code can not be null");
        }
        for (ExampleEnum value : values()) {
            if (value.code.equals(code)) {
                return value;
            }
        }
        return null;
    }
    @Data
    @SpelValid
    public class ParamTestBean {

        /**
         * 枚举值校验
         * <p>
         * 通过静态方法调用,校验枚举值是否存在
         */
        @SpelAssert(assertTrue = " T(cn.sticki.validator.spel.enums.ExampleEnum).getByCode(#this.testEnum) != null ", message = "枚举值不合法")
        private Integer testEnum;

    }
      ParamTestBean bean = new ParamTestBean();
      bean.setTestEnum(null);

当使用如上面bean的对象去进行校验时,我们希望得到一个IllegalArgumentException,但实际上会得到一个ValidationException:

image

要从框架层面去解决这个问题,只能够脱离 javax.validation 的规范和 hibernate 的执行器来独自进行校验,目前看来这样做的成本比较大,且会带来一些其他的影响,暂时不考虑这样做。

还有一种解决方案,需要使用框架的开发者进行一点特殊处理。当捕获到 ValidationException e 时,首先判断下 e.getCause() 的类型是不是自己项目中的业务异常基类,如果是业务异常的类型,就丢给对应的方法去处理,像这样:

@ExceptionHandler({BusinessException.class})
public Resp<Void> handleServiceException(BusinessException ex) {
  return new Resp<>(ex.getCode(), ex.getMessage());
}

@ExceptionHandler({ValidationException.class})
public Resp<Void> handleValidationException(ValidationException ex) {
  if (ex.getCause() instanceof BusinessException) {
      return handleBindException((BindException) ex.getCause());
  }
  return new Resp<>(500, "system error");
}

这种方案也有缺点,如果没有一个合适的基类,那么需要将多种不同的异常类型都进行特殊处理,比较麻烦。

目前决定还是先采用这种方案,后续有机会再做独立吧。