问题描述
我创建了一个小示例项目,以显示我在Spring Boot验证的配置及其与Hibernate集成中遇到的两个问题.我已经尝试过找到有关该主题的其他答复,但不幸的是,它们对我没有用,或者要求禁用Hibernate验证.
I created a small example project to show two problems I'm experiencing in the configuration of Spring Boot validation and its integration with Hibernate.I already tried other replies I found about the topic but unfortunately they didn't work for me or that asked to disable Hibernate validation.
我想使用实现ConstraintValidator<ValidUser, User>
的自定义验证器,并在其中插入我的UserRepository
.同时,我想保留Hibernate的默认行为,该行为在更新/持久化过程中检查验证错误.
I want use a custom Validator implementing ConstraintValidator<ValidUser, User>
and inject in it my UserRepository
.At the same time I want to keep the default behaviour of Hibernate that checks for validation errors during update/persist.
我在此处为应用程序的主要部分写信.
I write here for completeness main sections of the app.
自定义配置在此类中,我使用自定义MessageSource
设置了自定义验证器,因此Spring将从文件resources/messages.properties
Custom configurationIn this class I set a custom validator with a custom MessageSource
, so Spring will read messages from the file 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
The beanNothing special here if not the custom validator @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验证,则存储库始终为null(如果不是).
Custom validatorHere is where I try to inject the repository. The repository is always null if not when I disable Hibernate validation.
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)
在我的测试中,您可以从源代码中尝试,我有两个问题:
In my tests, you can try from the source code, I've two problems:
- 如果我不禁用Hibernate验证(spring.jpa.properties.javax.persistence.validation.mode = none),则
UserValidator
中注入的存储库为null - 即使我禁用了Hibernate验证程序,我的测试用例也会失败,因为某些事情阻止了Spring使用默认字符串内插法来验证消息,这些消息应该类似于[Constraint].[class name小写].[propertyName].我不想将约束注释与像
@NotBlank(message="{mycustom.message}")
这样的value元素一起使用,因为我看不到考虑到他对插值有自己的对流的观点,并且我可以利用它……这意味着更少的编码.
- The injected repository inside
UserValidator
is null if I don't disable Hibernate validation (spring.jpa.properties.javax.persistence.validation.mode=none) - Even if I disable Hibernate validator, my test cases fail because something prevent Spring to use the default string interpolation for validation messages that should be something like [Constraint].[class name lowercase].[propertyName]. I don't want to use the constraint annotation with the value element like this
@NotBlank(message="{mycustom.message}")
because I don't see the point considering that has his own convetion for interpolation and I can take advantage of that...that means less coding.
我附上了代码;您可以只运行Junit测试并查看错误(启用了Hibernate验证,请检查application.properties).
I attach the code; you can just run Junit tests and see errors (Hibernate validation is enable, check application.properties).
我做错了什么?我该怎么做才能解决这两个问题?
What am I doing wrong? What could I do to solve those two problems?
======更新======
====== UPDATE ======
请澄清一下,阅读Spring验证文档 https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints 他们说:
Just to clarify, reading Spring validation documentation https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation-beanvalidation-spring-constraints they say:
如您所见,ConstraintValidator实现可能像其他任何Spring bean一样具有@Autowired依赖.
As you can see, a ConstraintValidator implementation may have its dependencies @Autowired like any other Spring bean.
在我的配置类中,我编写了LocalValidatorFactoryBean
.
In my configuration class I created my LocalValidatorFactoryBean
as they write.
Another interesting questions are this and this, but I had not luck with them.
======更新2 ======
====== UPDATE 2 ======
大量研究之后,似乎没有使用Hibernate验证程序进行注入.
After a lot of reseach, seems with Hibernate validator the injection is not provided.
我找到了几种方法可以做到这一点:
I found a couple of way you can do that:
第一种方法
创建此配置类:
@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);
}
}
第二种方式
创建实用程序bean
Create an utility bean
@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);
}
}
,然后在验证器中初始化:
and then in the validator initialization:
@Override
public void initialize(ValidUser constraintAnnotation) {
userRepository = BeanUtil.getBean(UserRepository.class);
em = BeanUtil.getBean(EntityManager.class);
}
非常重要
在两种情况下,为了使它起作用,您必须以这种方式重置"实体管理器:
In both cases, in order to make the it works you have to "reset" the entity manager in this way:
@Override
public boolean isValid(User value, ConstraintValidatorContext context) {
try {
em.setFlushMode(FlushModeType.COMMIT);
//your code
} finally {
em.setFlushMode(FlushModeType.AUTO);
}
}
无论如何,我不知道这是否真的是一种安全的方法. 可能完全不是访问持久层的好习惯.
Anyway, I don't know if this is really a safe way. Probably it's not a good practice access to the persistence layer at all.
推荐答案
如果您确实需要在Validator中使用注入,请尝试在其上添加@Configurable
批注:
If you really need to use injection in your Validator try adding @Configurable
annotation on it:
@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;
}
}
从文档到该注释:
因此,这应该可以解决您的null问题.为了使它工作,您需要配置AspectJ ...(检查如何在Spring中使用@Configurable)
So this should solve your null problem. To make it work though, you would need to configure AspectJ... (Check how to use @Configurable in Spring for that)
这篇关于使用Spring 4和消息插值配置在ConstraintValidator内注入存储库的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!