对于 Java 程序员来说,接口的参数校验一般是通过 、 、 (min = 0, max = 255) 等这些注解来校验的。
但是有一些情况下,这些注解无法满足校验的需求,比如下面这段代码:
public class Test {

/**

  • 状态,值必须为枚举类 Status 中的 code
    */
    Integer status;

/**

  • 是否开启语音提示
    */
    Boolean enableVoice;

/**

  • 语音提示内容
    */
    String voiceContent;

}

这里涉及到两种参数校验是现有的注解无法满足的:

状态的 Int 值必须存在于枚举类中
当开启语音提示时,内容必须不为空,本质上是上下文关联的参数校验

这里我只是进行举例,我主要是想收集一下大家遇到的类型情况,然后我想做一个组件,能够尽可能的解决这些问题。
其实我已经有一个这样的组件了,但是担心自己考虑到的情况还不太够,所以想收集一下大家的建议,之后完善一下然后开源出来给大家使用。

我目前实现的这套东西是基于 spel + 注解 的,本来想到V站问一下有需求的场景,没想到得到了这么多关于这个组件的评价😅,目前的情况是,我发布这个帖子到现在以来还没有收集到一个有用的用例场景😅。
我实现的这套东西在使用上绝对是没毛病的,大大的提升了便利,举个例子:
public class TestParamVo {

/**

  • 状态,值必须为枚举类 StatusEnum 中的 code
    */
    @SpelAssert(" StatusEnum.getCode(#this.status) != null ") // 当表达式成立时校验通过
    Integer status;

/**

  • 是否开启语音提示
    */
    Boolean enableVoice;

/**

  • 语音提示内容
    */
    @SpelNotNull(" enableVoice == true ") // 当表达式成立时,注解标记的值必须不为null
    String voiceContent;

}

这是我目前实现的东西,由于是基于 Spel 的,所以可用性很高,如果你需要,甚至可以直接调用 spring bean(当然我不建议这样做),比如这样:
public class TestParamVo {

/**

  • 用户id,必须是有效的用户
    */
    @SpelAssert(" userService.getById(#this.userId) != null ") // 当表达式成立时校验通过
    Integer userId;

}

这只是部分简单的demo,我实现的东西比这个更完善。另外,这套组件确实基于 ConstraintValidator,但远不是简单的自定义一个 Validator 这么简单的,因为 ConstraintValidator 中只能拿到当前标记的字段值,无法获取到上下文的其他字段(enableVoice那个场景解决不了)。
希望大家尽可能提供用例,而不是劝退我,谢谢。

你是否在寻找 javax.validation.constraints.AssertTrue

复杂的参数校验直接 service 里写了,上下文关联的,有些还要查数据库校验的,很难做到一个组件通吃的。

看起来并不是

我只做那些不用查库的,另外我只是想收集这些例子🤡

简单解决下常用问题就行了,别搞太复杂。你是可以搞个解决所有问题的 DSL 来做参数验证,但到了那阶段还不如直接敲程序解决了

之前碰到一个,前端传入状态值的,我想用判断状态值是否在已有枚举中,或者在集合里面,我找了下好像没有支持这个情形的注解?

你是否在寻找 javax.validation.ConstraintValidator

是的,我就是想收集这些无法使用现有注解来进行校验的情况其实我是只希望大家告诉我这些情况就好了,不用跟我说其他劝退的话😅(这句不是跟 说的)

ConstraintValidator 可以解决一部分问题,但不够通用,比如我上面举例的第 2 点,enable 的那个,用 ConstraintValidator 是无法解决的

#9 理论上来说应该是可以的,定义注解的目标参数和依赖参数,然后定义 Validator 来写检查逻辑,但除非你有大量此类的依赖参数需要检查,如果只是一两个的话,这样更麻烦了。

public class NotIncludeNullValidator implements ConstraintValidator<NotIncludeNull, Collection<?>> { public void initialize(NotIncludeNull constraintAnnotation) { } public boolean isValid(Collection<?> value, ConstraintValidatorContext context) { // 不校验空集合 if (CollectionUtils.isEmpty(value)) { return true; } for (Object next : value) { if (next == null) { return false; } } return true; }}

没必要,我参数检验选择使用 spring 自带的断言 Assert ,写在参数类中,通过参数对象调用即可不明白为啥那么多写 jar 包的,特别是写一些垃圾代码的。让人看着就想喷

比如 Java bean 属性用基本类型,工具类是某个 apache 工具类套壳,或者内部本身存在 bug 或非常不合理的逻辑

github.com/making/yavi

加油,需要查库等的复杂逻辑,我一般写在代码中。

这个校验注解可以自定义,加类上都可以

你这可以在参数里面写个方法,并加上 注解 ,参考下我这例子javaclass ExampleParam{ private Integer code; (message = "你的业务逻辑提示") public ExampleEnum getCodeEnum() { // 根据 code 去获取 ExampleEnum ,然后返回 return ExampleEnum.getByValue(code); }}enum ExampleEnum{ ; private final Integer code; public static ExampleEnum getByValue(Integer value){ for (ExampleEnum exampleEnum : ExampleEnum.values()) { if(exampleEnum.getCode().equals(value)){ return exampleEnum; } } return null; }}

这样的话,在 Validate 框架去做校验的时候,也会去校验上面个的 getCodeEnum() 是否返回为空,如果返回为空的话,就会把 的 message 的信息给返回回去,这样就免去你在业务逻辑里面做参数校验了。这样也体现了面向对象编程的优势对吧?

如果 status 是枚举值,为什么不把 Integer 直接换成枚举类呢?有什么问题吗?

不是可以增加自定义的验证注解吗 ,通过 aop 的方式 复杂的业务验证也可以实现

也会有类似问题,通用的用提供的注解声明,这种情况判定来动态处理验证的,我目前放在业务实现里面校验处理,因为可能还需要对数据处理,比如 enableVoice 不等于 true ,如果前端传入了 voiceContent ,后端其实还类似需要清空这个值,那么顺手可以把校验 enableVoice=true 时 voiceContent 的非空校验做了,类似于if (BooleanUtils.isTrue(xx.getEnableVoice()) {}

if (BooleanUtils.isTrue(xx.getEnableVoice()) { // 校验 voiceContent 是否为空} else { xx.setVoiceContent(null);} 应该也可以实现 ConstraintValidator 来自定义校验规则,拓展注解之类的,只是目前是类似这样处理的

只有我一个人觉得 Boolean enableVoice 定义不规范吗,Boolean voiceSwitch 与 String voiceContent 对应,作为变量,enableVoice 应该是用作方法名

1.自定义校验注解和校验器2.建议手动校验吧,自动校验也可以实现,但是把业务判断逻辑放到配置中感觉不是太好

自定义验证+正则

可以看看 laravel 的 validation 设计,完爆其他所有语言,框架的校验目前比较需要的就是你提到的这个校验入参值是否在给定可选值中

对于枚举的验证做法:1. 硬代码, 通过 Assert 相关断言判断2. 通过 , 设定枚举边界.3. 通过实现 ConstraintValidator 对类进行校验.4. 通过代理 + 反射的方式添加自定义处理注解, 然后通过定义 spel 表达式确认是否校验通过.java(spel = "#p1.field != 0")用硬代码虽然会略显丑陋, 但是在新增枚举时可以不用改动代码.而 , 在枚举修改后需要同步修改, 如果没有相关注释说明会存在隐性 BUG.使用 ConstraintValidator 相对比较好, 因为能够自定义实现校验逻辑. 但是验证范围只能为对应类, 如果不做继承的话无法实现复用代理 + 反射会有略微性能损耗, 而 spel 表达式需要额外学习, 且有点耗费性能 (通过反射获取数据). 在多参数时需要在编译时增加 -parameters 否则参数名无法写入, spel 就无法获取到对应的对象, 校验就失败了.

通常的 aop 是基于代理模式实现,为了一个参数检验,引入更大的项目复杂度,消耗更多的服务器资源,你觉得可取吗?