美文网首页
新版本hibernate-validator7.x不生效问题

新版本hibernate-validator7.x不生效问题

作者: 二哈_8fd0 | 来源:发表于2022-03-13 14:53 被阅读0次
如果使用spring-boot-stater-validation,默认会集成hibernate-validator 6.x版本,如果想使用hibernate-validtor 7.x而java EE 在2018年命名空间全面迁移,由javax--> jakarta 则validation的api也由 javax-validation 迁移到了jakarta.validation,导致了失效。

可以看最新版本hibernate-validator 7.x的集成环境
7.0 series - Hibernate Validator

降低版本方式解决

直接使用spring-boot-stater-validation,默认hibernate-validator 6.x版本和
javax.validation-api 的版本 或者显示的使用下面maven的版本
jakarta.validation-api 2.x版本(也可以使用javax.validation-api因为2.x版本做了横向迁移)
如下图。spring-boot 新增独立依赖spring-boot-starter-parent或者 spring-boot-dependencies 的版本所以无需添加版本

    <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

如果不使用spring-boot-starter如下图

        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.2.3.Final</version>
        </dependency>

自定义初始化spring的Validator适配器

如果就想使用hibernate-validator7.x版本,可以使用自定义初始化逻辑,下面我们看看源码,可以先参考之前的源码解读javax.validation.constraints 及spring的整合,hibernate-validator的实现 - 简书 (jianshu.com)
前面的源码可以看到 spring和validator的集成通过一个适配器,首先通过参数解析器然后通过 dataBinder来调用校验,spring自己定义了org.springframework.validation.validator作为适配器接口,由SpringValidatorAdapter同时实现spring的适配器和被适配的javax.validation.Validator(新版本变成了jakarta.validation.Validator)那么我们进入源码也能看到spring的适配器还是实现的旧的javax.validation.Validator

image.png
可以看到全部使用的旧的命名空间,那么我们可不可以自己初始化这个适配器呢。继续看看源码。我们找到了最下面的一个子类,这个子类被spring注入了适配器接口 org.springframework.validation.validator
public class OptionalValidatorFactoryBean extends LocalValidatorFactoryBean {

    @Override
    public void afterPropertiesSet() {
        try {
            super.afterPropertiesSet();
        }
        catch (ValidationException ex) {
            LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex);
        }
    }

}

WebMvcConfigurationSupport 类进行注入适配器作为springmvc的controller参数校验

    @Bean
    public Validator mvcValidator() {
// 这里是一个模版方法,可以由子类覆盖,但是下面又做了类命名限定检查,只能使用旧版本api,所以这个方法覆盖并不能实现我们想要的
        Validator validator = getValidator();
        if (validator == null) {
// 这里看到指向的还是旧版本的命名空间
            if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
                Class<?> clazz;
                try {
                    String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
                    clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
                }
                catch (ClassNotFoundException | LinkageError ex) {
                    throw new BeanInitializationException("Failed to resolve default validator class", ex);
                }
                validator = (Validator) BeanUtils.instantiateClass(clazz);
            }
            else {
// 如果没有找到旧的命名空间就返回空适配器的实现,这个适配器并没有实现javax.validation.Validator来做适配
                validator = new NoOpValidator();
            }
        }
        return validator;
    }

我们可以在上面代码来自己初始化适配器,然后看到SpringValidatorAdapter的代码全部是引用的旧命名空间,new的话一定会NoClassDefFoundError的,spring为了支持多语言实际初始化的是OptionalValidatorFactoryBean,那么我们可以直将LocalValidatorFactoryBean和SpringValidatorAdapter和OptionalValidatorFactoryBean的代码复制出来,然后将所有命名空间替换即可.
要复制的类及功能如下
1.LocaleContextMessageInterpolator 实现MessageInterpolator接口对校验不通过的提示信息的实现

  1. SpringValidatorAdaptor 实现spring的适配器接口,及真正做校验的jakarta.validation.Validator接口,适配器的具体实现,做校验工作
  2. LocalValidatorFactoryBean 作为SpringValidatorAdaptor 适配器的子类用来整合spring的上下文,对当前适配器bean的管理及提示信息解析
  3. OptionalValidatorFactoryBean 适配器的真正实例化的子类
  4. SpringConstraintValidatorFactory 校验器工厂,例如@NotNull注解对应一个专门的非空判断的校验器,我们也可以自定义校验注解,实现的就是这个工厂实例化出来的。具体可以看代码ConstraintValidator<A extends Annotation, T>

我们将上面所有的类复制一份出来修改命名空间然后注入即可

    @Override
    @Bean
    public Validator mvcValidator() {
        return new CustomOptionalValidatorFactoryBean();
    }

启动之后还是不好用,我们通过debug初始化Validator的过程,发现报错

public class CustomOptionalValidatorFactoryBean extends CustomLocalValidatorFactoryBean {

    @Override
    public void afterPropertiesSet() {
        try {
            super.afterPropertiesSet();
        }
        catch (ValidationException ex) {
// 这里报错打印日志,发现
            ex.printStackTrace();
            LogFactory.getLog(getClass()).debug("Failed to set up a Bean Validation provider", ex);
        }
    }

}

但是启动程序还是不好用我们debug发现还是没有注入Validator,这里debug方式就不写了,我们直接在上面代码初始化时发现有报错

jakarta.validation.ValidationException: HV000183: Unable to initialize 'jakarta.el.ExpressionFactory'. Check that you have the EL dependencies on the classpath, or use ParameterMessageInterpolator instead
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.buildExpressionFactory(ResourceBundleMessageInterpolator.java:211)
    at org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator.<init>(ResourceBundleMessageInterpolator.java:97)
    at org.hibernate.validator.internal.engine.AbstractConfigurationImpl.getDefaultMessageInterpolator(AbstractConfigurationImpl.java:574)
    at io.g.erha.test.CustomLocalValidatorFactoryBean.afterPropertiesSet(CustomLocalValidatorFactoryBean.java:265)

发现是jakarta.el 包没发现,突然想起来最新的整合还需要依赖新得jakarta.el版本,新得版本el包抽出来了我们添加maven配置

      <dependency>
            <groupId>org.glassfish</groupId>
            <artifactId>jakarta.el</artifactId>
            <version>4.0.2</version>
        </dependency>

然后重新编译启动 。看见可以了

以上套路最好不要使用,就当是提供了另一种角度看spring和validator的整合逻辑,首先hibernate-validator是javaEE的javax.validation标准的实现,那么javaEE在2018年更改了命名空间后hibernate-validtor随后推出了7.x版本支持,但是spring-boot-starter一直使用旧的版本,我们可以从hibernate-validator的发布信息看到基本做了很少的改动,修复了少量bug,如果您就是遇到了7.x版本修复的bug的使用场景可以考虑使用上述方式提升版本,而通过上述的代码复制我们也知道了spring通过适配器方式整合validator结合springmvc进行参数校验,并提供了Spring上下文支持的javax.validation(jakarta.validation)的自定义校验器的工厂实现,因为我们默认注入的Validator都是spring的适配器,那么这个spring上下文的支持就是我们在自定义实现了ConstraintValidator<A extends Annotation, T> 接口通过自定义校验注解校验时,虽然这个校验器子类是每次实例化但是spring也会帮你@Autowired 和@Resource注入

最后我们看看spring官方也解释了为什么不集成新的命名空间版本,及未来会在今年的spring-boot 3.x 和 spring 6.x版本全面支持! 大家尽量等等喽

支持 Jakarta EE 9(jakarta.* 命名空间中的注释和接口) ·问题 #25354 ·弹簧项目/弹簧框架 (github.com)

相关文章

  • 新版本hibernate-validator7.x不生效问题

    如果使用spring-boot-stater-validation,默认会集成hibernate-validato...

  • Webpack SourceMap 配置

    最近项目中遇到了sourcemap不生效的问题,而且是有的项目生效,有的项目不生效, 经过一番查找资料,最终将问题...

  • 表格反选 Jquery实现

    JS 函数如下: 之前有看到设置值是用attr方法,新版本之后不生效,需要使用prop方法

  • 添加.gitignore不生效问题

    先把已修改的代码上传到远程仓库上,确保上传成功后,删除本地目录 删除本地缓存 将代码加到缓存区 提交

  • 修改.gitignore不生效问题

    项目打包后,在.gitignore.文件里面删除了dist,依然上传不成功。原因是.gitignore只能忽略那些...

  • spring @PropertySource 不生效问题

    在操作父子容器的过程中,发现@PropertySource无法被子容器继承。明明在代码里面已经看见已经继承了,但是...

  • Spring事务不生效问题

    问题 现有代码如下: 原因及解决思路 先获取Spring容器中 TransactionService 的 clas...

  • GPU Skinning不生效问题

    1)GPU Skinning不生效问题2)勾选凸包报的警告问题3)Unity 2019 图片压缩格式选择4)And...

  • 解决.gitignore不生效问题

    问题 在使用git进行版本控制的过程中发现,将想被忽略的文件(文件夹)配置到.gitignore文件中后,实际修改...

  • Spring事务不生效问题

    事务未生效可能造成严重的数据不一致性问题,因而保证事务生效至关重要。Spring事务是通过Spring aop实现...

网友评论

      本文标题:新版本hibernate-validator7.x不生效问题

      本文链接:https://www.haomeiwen.com/subject/glbrdrtx.html