303 dependency injection and Hibernate
Spring 3.0.2, Hibernate 3.5.0, Hibernate-Validator 4.0.2.GA
I am trying to inject Spring dependencies into a ConstraintValidator using:
@PersistenceContext
private EntityManager entityManager;
I have configured the application context with:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
Which, according to the Spring documentation, should allow “custom ConstraintValidators to benefit from dependency injection like any other Spring bean”
Within the debugger I can see Spring calling getBean to create the ConstraintValidator. Later when flush triggers the preInsert, a different ConstraintValidator is created and called. The problem is the EntityManager is null within this new ConstraintValidator. I've tried injecting other dependencies within the ConstraintValidator and these are always null.
Does anyone know if it is possible to inject dependencies into a ConstraintValidator?
The best way to inject a Spring context aware ValidatorFactory inside your EntityManager is by using the javax.persistence.validation.factory property. Configuration goes as follows:
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect" />
</bean>
</property>
<property name="jpaPropertyMap">
<map>
<entry key="javax.persistence.validation.factory" value-ref="validator" />
</map>
</property>
</bean>
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
Enjoy!
Although JSR-303 (Hibernate Validator being the reference implementation) instantiates our custom ConstraintValidator, it in effect delegates the actual creation to a ValidatorFactory. So you are right to think that using Spring's LocalValidatorFactoryBean should enable dependency injection for your custom validators throughout your application.
The subtlety here lies in the fact that Hibernate itself uses a separate ValidatorFactory than the one configured in your Spring context when handling entity lifecycle events (pre-update, pre-insert, pre-delete). This is by design I think: Hibernate Validator is not aware of Spring, so we must tell Hibernate to use Spring's LocalValidatorFactoryBean instead of creating its own.
Basically, something like this is required in your Spring application context XML:
<!-- The LocalValidatorFactoryBean is able to instantiate custom validators and inject autowired dependencies. -->
<bean id="validator"
class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<!--
This is the key to link Spring's injection to Hibernate event-based validation.
Notice the first constructor argument, this is our Spring ValidatorFactory instance.
-->
<bean id="beanValidationEventListener" class="org.hibernate.cfg.beanvalidation.BeanValidationEventListener">
<constructor-arg ref="validator"/>
<constructor-arg ref="hibernateProperties"/>
</bean>
<bean id="mySessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
...
<!--
A reference to our custom BeanValidationEventListener instance is pushed for all events.
This can of course be customized if you only need a specific event to trigger validations.
-->
<property name="eventListeners">
<map>
<entry key="pre-update" value-ref="beanValidationEventListener"/>
<entry key="pre-insert" value-ref="beanValidationEventListener"/>
<entry key="pre-delete" value-ref="beanValidationEventListener"/>
</map>
</property>
</bean>
Since I've struggled for a while to find out how to do it, I've put together a demo app here on Github The README file is self-explanatory.
It seems that in JPA2, the persistence provider's validation mechanism kicks in by default on pre-persist, pre-update, etc.
It uses its own mechanism for constructing validators, Spring doesn't get involved.
The only solution I can see at the moment is disabling the out-of-the-box JPA2 validation in persistence.xml:
<persistence-unit name="persistenceUnit" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<properties>
<property name="hibernate.show_sql" value="true"/>
<!-- other props -->
</properties>
<validation-mode>NONE</validation-mode>
</persistence-unit>
Then use Spring's LocalValidatiorFactoryBean as usual. You'll then have to call the validators manually as in the pre-JPA2 world.
UPDATE:
Just to make it complete, another solution would be to specify a constraint-validator-factory
in META-INF/validation.xml.
It would be very nice if this could be Spring's SpringConstraintValidatorFactory
but unfortunately, it requires an AutowireCapableBeanFactory
to be passed into its constructor, but a class with no-arg constructor is expected here by JPA2.
Which leaves the option of creating own implementation of ConstraintValidatorFactory
that pulls validators out of Spring, but I don't see any clean way of doing that.
上一篇: ContextLoaderListener在Spring中的角色/用途?
下一篇: 303依赖注入和Hibernate