SpringBoot 2.7.13版本中自定义ConstraintValidator里@Autowired注入ObjectMapper为Null的问题求助
嘿,我之前也碰到过这个坑!ConstraintValidator的实例化逻辑有点特殊,默认情况下它是由Hibernate Validator创建的,而非Spring容器,所以@Autowired注入就会失效。结合你的SpringBoot 2.7.13版本,给你几个亲测有效的解决方案:
先再明确下你的问题场景:
- 环境:SpringBoot 2.7.13 + Java 11
- 问题:自定义的
PhoneNumbersValidAndUniqueValidator实现了ConstraintValidator,其中@Autowired private ObjectMapper mapper;始终为Null,但在其他Spring组件中ObjectMapper注入完全正常 - 已经尝试过常见的注入解决方案,但都没生效
你的校验器代码如下:
package com.chisw.contactservice.validation; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.ArrayList; import java.util.List; import java.util.Optional; @Component public class PhoneNumbersValidAndUniqueValidator implements ConstraintValidator<PhoneNumbersValidAndUnique, String> { private static final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); @Autowired private ObjectMapper mapper; @Override public boolean isValid(String value, ConstraintValidatorContext context) { if (value == null) { return false; } List<String> phoneNumbers; try { phoneNumbers = mapper.readValue(value, new TypeReference<ArrayList<String>>() {}); } catch (JsonProcessingException e) { return false; } try { Optional<Phonenumber.PhoneNumber> invalidPhoneNumber = phoneNumbers.stream() .map(phone -> { try { return phoneNumberUtil.parse(phone, null); } catch (NumberParseException e) { throw new RuntimeException(e); } }) .filter(phoneNumber -> !phoneNumberUtil.isValidNumber(phoneNumber)) .findFirst(); if (invalidPhoneNumber.isPresent()) { return false; } } catch (RuntimeException e) { return false; } if (phoneNumbers.stream() .map(phoneNumber -> phoneNumber.replaceAll("[\\s\\-()]", "")) .distinct() .count() != phoneNumbers.size()){ return false; } return true; } }
亲测有效的解决方案
方案1:通过Spring上下文工具类手动获取Bean
这是最直接的解决方式,我们可以绕过Validator的实例化问题,直接从Spring上下文拿ObjectMapper:
首先创建一个Spring上下文持有类(这个类可以复用在其他需要手动拿Bean的场景):
import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; @Component public class SpringContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext; @Override public void setApplicationContext(ApplicationContext context) throws BeansException { applicationContext = context; } public static <T> T getBean(Class<T> beanClass) { return applicationContext.getBean(beanClass); } }
然后修改你的Validator代码,去掉@Autowired,在使用时手动获取:
// 去掉这行@Autowired注入 // @Autowired // private ObjectMapper mapper; @Override public boolean isValid(String value, ConstraintValidatorContext context) { // 在需要使用的地方,从上下文获取ObjectMapper ObjectMapper mapper = SpringContextHolder.getBean(ObjectMapper.class); // 后续的校验逻辑保持不变... }
方案2:通过配置类手动注入依赖
我们可以让Spring来管理Validator的实例,然后手动给它注入ObjectMapper:
首先修改Validator,添加setter方法去掉@Autowired:
// 去掉@Autowired // @Autowired // private ObjectMapper mapper; private ObjectMapper mapper; // 添加setter方法 public void setMapper(ObjectMapper mapper) { this.mapper = mapper; }
然后创建一个配置类,手动创建Validator实例并注入依赖:
import com.chisw.contactservice.validation.PhoneNumbersValidAndUniqueValidator; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class ValidatorConfig { @Bean public PhoneNumbersValidAndUniqueValidator phoneNumbersValidAndUniqueValidator(ObjectMapper objectMapper) { PhoneNumbersValidAndUniqueValidator validator = new PhoneNumbersValidAndUniqueValidator(); validator.setMapper(objectMapper); return validator; } }
这样Spring会负责创建Validator实例,并且把ObjectMapper注入进去,后续校验时就能正常使用了。
方案3:配置SpringValidatorAdapter让Spring管理所有Validator
这种方式可以从根源上解决问题,让所有的Validator都由Spring容器管理,这样@Autowired就能正常工作:
创建一个配置类,配置Spring的Validator适配器:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; import org.springframework.validation.beanvalidation.SpringValidatorAdapter; @Configuration public class ValidatorConfig { @Bean public SpringValidatorAdapter springValidatorAdapter() { LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean(); factoryBean.afterPropertiesSet(); return new SpringValidatorAdapter(factoryBean); } }
之后在你需要使用校验的地方,注入SpringValidatorAdapter而不是默认的javax.validation.Validator,这样所有自定义的ConstraintValidator都会由Spring管理,@Autowired注入就能正常生效了。
备注:内容来源于stack exchange,提问作者Maxim Kosey

