You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring自定义ConstraintValidator时@Value注入为null问题

问题描述

使用ConstraintValidator实现类做参数校验,想要读取application.properties中配置的字符高度阈值时,加了@Value注解的minimumHeight字段始终为null,尝试构造器注入时抛出如下错误:

java.lang.NoSuchMethodException: com.miquido.Validator.CharacterValidator.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3585) ~[na:na]
at java.base/java.lang.Class.getConstructor(Class.java:2271) ~[na:na]
at org.hibernate.validator.internal.util.privilegedactions.NewInstance.run(NewInstance.java:41) ~[hibernate-validator-6.2.3.Final.jar:6.2.3.Final]

初始校验器与自定义注解代码如下:

import org.springframework.beans.factory.annotation.Value;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;


public class CharacterValidator implements ConstraintValidator<ValidCharacter, Long> {

    @Autowired
    private ConfigProperties configProperties;

    @Override
    public void initialize(ValidCharacter constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(Long value, ConstraintValidatorContext context) {
        configProperties = SpringApplicationContextService.getApplicationContext().getBean(ConfigProperties.class);
        if (value == null) {
            return true;
        }
        formatMessage(context);
        return value >= configProperties.getMinimum();
    }

    private void formatMessage(ConstraintValidatorContext context) {
        String msg = context.getDefaultConstraintMessageTemplate();
        String formattedMsg = String.format(msg, configProperties.getMinimum());
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(formattedMsg)
                .addConstraintViolation();
    }
}
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CharacterValidator.class)
public @interface ValidCharacter {

    String message() default "Character must be higher than %s";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

问题排查记录

  • 第一次按照网上建议修改代码后,minimumHeight字段仍然为null
  • 第二次新增实现ApplicationContextAware接口的Spring上下文工具类,尝试手动从容器获取Bean,工具类代码如下:
@Service
public class SpringApplicationContextService implements ApplicationContextAware {

    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        SpringApplicationContextService.applicationContext = applicationContext;
    }
}

根因说明

默认情况下ConstraintValidator实例是Hibernate Validator自己通过反射调用无参构造直接new出来的,不归Spring容器管理,这就是所有问题的来源:

  • 字段上标注@Autowired、@Value完全不生效,因为Spring没有机会给这些实例做依赖注入
  • 给校验器写了有参构造后,Hibernate Validator找不到默认无参构造,就直接抛出你看到的NoSuchMethodException
    你写的手动从上下文取Bean的方案理论上能跑,但要满足两个前提:一是SpringApplicationContextService在Spring的包扫描路径下,能被正常实例化;二是触发校验的时候Spring容器已经启动完成,静态的applicationContext已经完成赋值。如果容器刷新阶段就触发了校验,这个静态字段还是null,而且这种硬编码拿Bean的写法侵入性强,不是推荐方案。

解决方案

核心思路是让Spring负责创建校验器实例,而不是让Hibernate Validator自己new对象,这样就能直接支持Spring的依赖注入,不需要额外写上下文工具类。

  1. 给CharacterValidator加上@Component注解,把它交给Spring容器管理:
@Component // 新增这个注解
public class CharacterValidator implements ConstraintValidator<ValidCharacter, Long> {
    // 直接注入即可,不需要手动调用getBean
    @Autowired
    private ConfigProperties configProperties;

    @Override
    public void initialize(ValidCharacter constraintAnnotation) {
        // 原代码里的super调用可以删掉,默认实现是空逻辑
    }

    @Override
    public boolean isValid(Long value, ConstraintValidatorContext context) {
        // 删掉手动获取Bean的冗余代码
        if (value == null) {
            return true;
        }
        formatMessage(context);
        return value >= configProperties.getMinimum();
    }

    private void formatMessage(ConstraintValidatorContext context) {
        String msg = context.getDefaultConstraintMessageTemplate();
        String formattedMsg = String.format(msg, configProperties.getMinimum());
        context.disableDefaultConstraintViolation();
        context.buildConstraintViolationWithTemplate(formattedMsg)
                .addConstraintViolation();
    }
}
  1. 如果是老版本Spring Boot,或者自己手动配置过Validator逻辑,新增配置类指定校验器工厂,让Spring接管校验器的实例化:
@Configuration
public class ValidatorConfig {
    @Bean
    public Validator validator(AutowireCapableBeanFactory springFactory) {
        return Validation.byProvider(HibernateValidator.class)
                .configure()
                // 核心配置:使用Spring的工厂创建校验器实例,支持依赖注入
                .constraintValidatorFactory(new SpringConstraintValidatorFactory(springFactory))
                .buildValidatorFactory()
                .getValidator();
    }
}

高版本Spring Boot只要给校验器加@Component注解就能自动识别,不需要额外写这个配置。如果加了注解还是注入为null,再补充上述配置即可。
如果一定要用手动拿Bean的方案,先检查SpringApplicationContextService是否在启动类的扫描包范围内,有没有被Spring成功加载,避免容器未初始化完成就调用静态方法获取上下文。

内容的提问来源于stack exchange,提问作者adiz10

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 05:42:13