使用Spring 4和消息插值配置在ConstraintValidator中注入库
我创建了一个小的示例项目,以显示我在配置Spring Boot验证及其与Hibernate集成时遇到的两个问题。 我已经尝试了其他关于该主题的回复,但不幸的是,它们不适用于我或者要求禁用Hibernate验证。使用Spring 4和消息插值配置在ConstraintValidator中注入库
我想使用自定义验证器实现ConstraintValidator<ValidUser, User>
并注入它我的UserRepository
。 同时我想保持Hibernate的默认行为,在更新/保留期间检查验证错误。
我在这里写的完整性主要部分的应用程序。
自定义配置 在这个类中我设置一个自定义的验证与自定义MessageSource
,所以Spring将从文件读取消息resources/messages.properties
@Configuration
public class CustomConfiguration {
@Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:/messages");
messageSource.setUseCodeAsDefaultMessage(false);
messageSource.setCacheSeconds((int) TimeUnit.HOURS.toSeconds(1));
messageSource.setFallbackToSystemLocale(false);
return messageSource;
}
@Bean
public LocalValidatorFactoryBean validator() {
LocalValidatorFactoryBean factoryBean = new LocalValidatorFactoryBean();
factoryBean.setValidationMessageSource(messageSource());
return factoryBean;
}
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
MethodValidationPostProcessor methodValidationPostProcessor = new MethodValidationPostProcessor();
methodValidationPostProcessor.setValidator(validator());
return methodValidationPostProcessor;
}
}
豆子 这里没有什么特别,如果没有自定义验证器@ValidUser
@ValidUser
@Entity
public class User extends AbstractPersistable<Long> {
private static final long serialVersionUID = 1119004705847418599L;
@NotBlank
@Column(nullable = false)
private String name;
/** CONTACT INFORMATION **/
@Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String landlinePhone;
@Pattern(regexp = "^\\+{1}[1-9]\\d{1,14}$")
private String mobilePhone;
@NotBlank
@Column(nullable = false, unique = true)
private String username;
@Email
private String email;
@JsonIgnore
private String password;
@Min(value = 0)
private BigDecimal cashFund = BigDecimal.ZERO;
public User() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getLandlinePhone() {
return landlinePhone;
}
public void setLandlinePhone(String landlinePhone) {
this.landlinePhone = landlinePhone;
}
public String getMobilePhone() {
return mobilePhone;
}
public void setMobilePhone(String mobilePhone) {
this.mobilePhone = mobilePhone;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public BigDecimal getCashFund() {
return cashFund;
}
public void setCashFund(BigDecimal cashFund) {
this.cashFund = cashFund;
}
}
自定义验证器 这里是我尝试注入存储库的地方。当我禁用Hibernate验证时,存储库始终为空。
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
@Autowired
private UserRepository userRepository;
@Override
public void initialize(ValidUser constraintAnnotation) {
}
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername(value.getUsername());
if (foundUser != null && foundUser.getId() != value.getId()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error("", e);
return false;
}
return true;
}
}
messages.properties
#CUSTOM VALIDATORS
ValidUser.message = I dati inseriti non sono validi. Verificare nuovamente e ripetere l'operazione.
ValidUser.unique.username = L'username [${validatedValue.getUsername()}] è già stato utilizzato. Sceglierne un altro e ripetere l'operazione.
#DEFAULT VALIDATORS
org.hibernate.validator.constraints.NotBlank.message = Il campo non può essere vuoto
# === USER ===
Pattern.user.landlinePhone = Il numero di telefono non è valido. Dovrebbe essere nel formato E.123 internazionale (https://en.wikipedia.org/wiki/E.123)
在我的测试中,可以从源代码试试,我有两个问题:
- 内
UserValidator
注入的仓库是空如果我不禁用Hibernate验证(spring.jpa.properties.javax.persistence.validation.mode = none) - 即使我禁用Hibernate验证器,我的测试用例也会失败,因为某些情况会阻止Spring使用默认字符串插值来验证消息,该消息应该是[Constraint]。[class name lowercase]。[propertyName]。我不想使用带有这个值元素的约束注释,就像这个
@NotBlank(message="{mycustom.message}")
一样,因为我没有看到考虑到它有自己的内插对应点,我可以利用那个......这意味着更少的编码。
;您可以运行Junit测试并查看错误(Hibernate验证已启用,请检查application.properties)。
我在做什么错?我能做些什么来解决这两个问题?
====== UPDATE ======
只是为了澄清,读春天验证文件https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints他们说:
默认情况下,LocalValidatorFactoryBean配置使用Spring时,SpringConstraintValidatorFactory创建ConstraintValidator实例。这允许您的自定义ConstraintValidators像任何其他Spring bean一样受益于依赖注入。如你所见,一个ConstraintValidator实现可能像其他Spring bean一样拥有@Autowired的依赖关系。
在我的配置类中,我在他们编写时创建了我的LocalValidatorFactoryBean
。
======更新2 ======
很多研制后,似乎有不提供注入Hibernate的验证。
我发现一对夫妇的方式,你可以这样做:
1路
创建此配置类:
@Configuration
public class HibernateValidationConfiguration extends HibernateJpaAutoConfiguration {
public HibernateValidationConfiguration(DataSource dataSource, JpaProperties jpaProperties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, jpaProperties, jtaTransactionManager, transactionManagerCustomizers);
}
@Autowired
private Validator validator;
@Override
protected void customizeVendorProperties(Map<String, Object> vendorProperties) {
super.customizeVendorProperties(vendorProperties);
vendorProperties.put("javax.persistence.validation.factory", validator);
}
}
第二方式
创建工具豆
@Service
public class BeanUtil implements ApplicationContextAware {
private static ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
context = applicationContext;
}
public static <T> T getBean(Class<T> beanClass) {
return context.getBean(beanClass);
}
}
,然后在验证初始化:
@Override
public void initialize(ValidUser constraintAnnotation) {
userRepository = BeanUtil.getBean(UserRepository.class);
em = BeanUtil.getBean(EntityManager.class);
}
非常重要
在这两种情况下,为了使这种方式它的工作原理,你必须“复位”的实体管理器:
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
em.setFlushMode(FlushModeType.COMMIT);
//your code
} finally {
em.setFlushMode(FlushModeType.AUTO);
}
}
无论如何,我不知道这是否真的是一种安全的方式。 Probably it's not a good practice access to the persistence layer at all。
如果你真的需要在验证使用注射尝试将它添加@Configurable
注释:
@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class UserValidator implements ConstraintValidator<ValidUser, User> {
private Logger log = LogManager.getLogger();
@Autowired
private UserRepository userRepository;
// this initialize method wouldn't be needed if you use HV 6.0 as it has a default implementation now
@Override
public void initialize(ValidUser constraintAnnotation) {
}
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
User foundUser = userRepository.findByUsername(value.getUsername());
if (foundUser != null && foundUser.getId() != value.getId()) {
context.disableDefaultConstraintViolation();
context.buildConstraintViolationWithTemplate("{ValidUser.unique.username}").addConstraintViolation();
return false;
}
} catch (Exception e) {
log.error("", e);
return false;
}
return true;
}
}
从文档到注释:
标记一个类为符合春季 - 驱动配置
所以这应该解决您的空问题。为了使它工作,你需要配置AspectJ ...(请查看如何在Spring中使用@Configurable)
感谢您的回复。我更新了我的问题,指出依赖注入应该没有根据他们说什么的特殊技巧。但是我试图按照自己的方式使用EnableSpringConfigured和EnableLoadTimeWeaving,但注入的存储库始终为空。 – drenda