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的依赖注入,不需要额外写上下文工具类。
- 给
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(); } }
- 如果是老版本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
相关产品推荐
相关产品推荐

