![](/img/trans.png)
[英]Injecting the application TransactionManager into a JPA EntityListener
[英]Injecting a Spring dependency into a JPA EntityListener
我正在嘗試將 Spring 依賴項注入JPA EntityListener 。 這是我的監聽器類:
@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class PliListener {
@Autowired
private EvenementPliRepository evenementPliRepository;
@PostPersist
void onPostPersist(Pli pli) {
EvenementPli ev = new EvenementPli();
ev.setPli(pli);
ev.setDateCreation(new Date());
ev.setType(TypeEvenement.creation);
ev.setMessage("Création d'un pli");
System.out.println("evenementPliRepository: " + evenementPliRepository);
evenementPliRepository.save(ev);
}
}
這是我的實體類:
@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
但是,我的依賴項(即evenementPliRepository
)始終為 null 。
有人可以幫忙嗎?
在無狀態 bean 上注入依賴項的一個技巧是將依賴項定義為“靜態”,創建一個 setter 方法,以便 Spring 可以注入依賴項(將其分配給靜態依賴項)。
將依賴聲明為靜態。
static private EvenementPliRepository evenementPliRepository;
創建一個方法,以便 Spring 可以注入它。
@Autowired
public void init(EvenementPliRepository evenementPliRepository)
{
MyListenerClass.evenementPliRepository = evenementPliRepository;
logger.info("Initializing with dependency ["+ evenementPliRepository +"]");
}
更多詳細信息,請訪問: http : //blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html
這實際上是一個老問題,但我找到了一個替代解決方案:
public class MyEntityListener {
@Autowired
private ApplicationEventPublisher publisher;
@PostPersist
public void postPersist(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnCreatedEvent<>(this, target));
}
@PostUpdate
public void postUpdate(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnUpdatedEvent<>(this, target));
}
@PostRemove
public void postDelete(MyEntity target) {
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(new OnDeletedEvent<>(this, target));
}
}
可能不是最好的,但比沒有 AOP + 編織的靜態變量更好。
我用@Component注解注解了監聽器,然后創建了一個非靜態的setter來分配注入的Spring bean,效果很好
我的代碼看起來像:
@Component
public class EntityListener {
private static MyService service;
@Autowired
public void setMyService (MyService service) {
this.service=service;
}
@PreUpdate
public void onPreUpdate() {
service.doThings()
}
@PrePersist
public void onPersist() {
...
}
}
這個解決方案呢?
@MappedSuperclass
@EntityListeners(AbstractEntityListener.class)
public abstract class AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id")
private Long id;
@Column(name = "creation_date")
private Date creationDate;
@Column(name = "modification_date")
private Date modificationDate;
}
那么聽者...
@Component
public class AbstractEntityListener {
@Autowired
private DateTimeService dateTimeService;
@PreUpdate
public void preUpdate(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
abstractEntity.setModificationDate(this.dateTimeService.getCurrentDate());
}
@PrePersist
public void prePersist(AbstractEntity abstractEntity) {
AutowireHelper.autowire(this, this.dateTimeService);
Date currentDate = this.dateTimeService.getCurrentDate();
abstractEntity.setCreationDate(currentDate);
abstractEntity.setModificationDate(currentDate);
}
}
還有幫手...
/**
* Helper class which is able to autowire a specified class. It holds a static reference to the {@link org
* .springframework.context.ApplicationContext}.
*/
public final class AutowireHelper implements ApplicationContextAware {
private static final AutowireHelper INSTANCE = new AutowireHelper();
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
/**
* Tries to autowire the specified instance of the class if one of the specified beans which need to be autowired
* are null.
*
* @param classToAutowire the instance of the class which holds @Autowire annotations
* @param beansToAutowireInClass the beans which have the @Autowire annotation in the specified {#classToAutowire}
*/
public static void autowire(Object classToAutowire, Object... beansToAutowireInClass) {
for (Object bean : beansToAutowireInClass) {
if (bean == null) {
applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
}
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
/**
* @return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
}
為我工作。
來源: http : //guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/
從 Spring V5.1(和 Hibernate V5.3)開始,它應該開箱即用,因為 Spring 注冊為這些類的提供者。 查看SpringBeanContainer 的文檔
我開始走上使用 AOP 將 spring bean 注入實體偵聽器的路徑。 經過一天半的研究和嘗試不同的東西后,我發現了這個鏈接,它說:
不可能將 spring 管理的 bean 注入 JPA EntityListener 類。 這是因為 JPA 偵聽器機制應該基於無狀態類,因此這些方法實際上是靜態的,並且不受上下文影響。 ... 再多的 AOP 也拯救不了你,沒有任何東西被注入到代表偵聽器的“對象”中,因為實現實際上並沒有創建實例,而是使用了類方法。
在這一點上,我重新組合並偶然發現了 EclipseLink DescriptorEventAdapter 。 使用這些信息,我創建了一個擴展描述符適配器的偵聽器類。
public class EntityListener extends DescriptorEventAdapter {
private String injectedValue;
public void setInjectedValue(String value){
this.injectedValue = value;
}
@Override
public void aboutToInsert(DescriptorEvent event) {
// Do what you need here
}
}
為了使用該類,我可以在實體類上使用 @EntityListeners 注釋。 不幸的是,這種方法不允許 Spring 控制我的監聽器的創建,因此不允許依賴注入。 相反,我在我的班級中添加了以下“init”函數:
public void init() {
JpaEntityManager entityManager = null;
try {
// Create an entity manager for use in this function
entityManager = (JpaEntityManager) entityManagerFactory.createEntityManager();
// Use the entity manager to get a ClassDescriptor for the Entity class
ClassDescriptor desc =
entityManager.getSession().getClassDescriptor(<EntityClass>.class);
// Add this class as a listener to the class descriptor
desc.getEventManager().addListener(this);
} finally {
if (entityManager != null) {
// Cleanup the entity manager
entityManager.close();
}
}
}
添加一點 Spring XML 配置
<!-- Define listener object -->
<bean id="entityListener" class="EntityListener " init-method="init">
<property name="injectedValue" value="Hello World"/>
<property name="entityManagerFactory" ref="emf"/>
</bean>
現在我們有這樣一種情況,Spring 創建一個實體偵聽器,將所需的任何依賴項注入它,然后偵聽器對象將自己注冊到它打算偵聽的實體類。
我希望這有幫助。
我測試了https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/ 中建議的方法並工作。 不是很干凈,但可以完成工作。 對我來說稍微修改的 AutowireHelper 類看起來像這樣:
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class AutowireHelper implements ApplicationContextAware {
private static ApplicationContext applicationContext;
private AutowireHelper() {
}
public static void autowire(Object classToAutowire) {
AutowireHelper.applicationContext.getAutowireCapableBeanFactory().autowireBean(classToAutowire);
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
}
然后從實體偵聽器調用它,如下所示:
public class MyEntityAccessListener {
@Autowired
private MyService myService;
@PostLoad
public void postLoad(Object target) {
AutowireHelper.autowire(this);
myService.doThings();
...
}
public void setMyService(MyService myService) {
this.myService = myService;
}
}
JPA 偵聽器的問題在於:
它們不是由 Spring 管理的(所以沒有注入)
它們是(或可能)在Spring 的應用程序上下文准備好之前創建的(因此我們不能在構造函數調用中注入 bean)
我處理這個問題的解決方法:
1) 使用公共靜態LISTENERS
字段創建Listener
類:
public abstract class Listener {
// for encapsulation purposes we have private modifiable and public non-modifiable lists
private static final List<Listener> PRIVATE_LISTENERS = new ArrayList<>();
public static final List<Listener> LISTENERS = Collections.unmodifiableList(PRIVATE_LISTENERS);
protected Listener() {
PRIVATE_LISTENERS.add(this);
}
}
2) 我們想要添加到Listener.LISTENERS
所有 JPA 監聽Listener.LISTENERS
都必須擴展這個類:
public class MyListener extends Listener {
@PrePersist
public void onPersist() {
...
}
...
}
3) 現在我們可以在 Spring 的 Application Context 准備好之后獲取所有監聽器並注入 bean
@Component
public class ListenerInjector {
@Autowired
private ApplicationContext context;
@EventListener(ContextRefreshedEvent.class)
public void contextRefreshed() {
Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
}
}
嘗試像這樣使用ObjectFactory
@Configurable
public class YourEntityListener {
@Autowired
private ObjectFactory<YourBean> yourBeanProvider;
@PrePersist
public void beforePersist(Object target) {
YourBean yourBean = yourBeanProvider.getObject();
// do somthing with yourBean here
}
}
我在 spring-data-jpa 的org.springframework.data.jpa.domain.support.AuditingEntityListener
找到了這個解決方案。
演示: https : //github.com/eclipseAce/inject-into-entity-listener
我相信這是因為這個偵聽器 bean 不受 Spring 的控制。 Spring 沒有實例化它,Spring 怎么知道如何找到那個 bean 並進行注入?
我還沒有嘗試過,但似乎您可以使用帶有 Spring 的 Configurable 注釋的 AspectJ Weaver 來讓 Spring 控制非 Spring 實例化的 bean。
另外一個選擇:
創建一個服務以使 ApplicationContext 可訪問:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;
import lombok.Setter;
@Service
class ContextWrapper {
@Setter
private static ApplicationContext context;
@Autowired
public ContextWrapper(ApplicationContext ac) {
setContext(ac);
}
public static ApplicationContext getContext() {
return context;
}
}
用它:
...
public class AuditListener {
private static final String AUDIT_REPOSITORY = "AuditRepository";
@PrePersist
public void beforePersist(Object object){
//TODO:
}
@PreUpdate
public void beforeUpdate(Object object){
//TODO:
}
@PreRemove
public void beforeDelete(Object object) {
getRepo().save(getAuditElement("DEL",object));
}
private Audit getAuditElement(String Operation,Object object){
Audit audit = new Audit();
audit.setActor("test");
Timestamp timestamp = new Timestamp(System.currentTimeMillis());
audit.setDate(timestamp);
return audit;
}
private AuditRepository getRepo(){
return ContextWrapper.getContext().getBean(AUDIT_REPOSITORY, AuditRepository.class);
}
}
這個類是作為 jpa 的偵聽器創建的:
...
@Entity
@EntityListeners(AuditListener.class)
@NamedQuery(name="Customer.findAll", query="SELECT c FROM Customer c")
public class Customer implements Serializable {
private static final long serialVersionUID = 1L;
...
由於偵聽器不受 Spring 的控制,因此它無法訪問上下文 bean。 我嘗試了多個選項(@Configurable (...)),除了創建一個靜態訪問上下文的類之外,沒有一個有效。 已經陷入困境,我認為這是一個優雅的選擇。
在我看來,最自然的方式是介入 EntityListener 的實例化過程。 這種方式在 Hibernate 5.3 之前的版本和 5.3 之后的版本中顯着不同。
1)在 5.3 之前的 Hibernate 版本中org.hibernate.jpa.event.spi.jpa.ListenerFactory
負責 EntityListener 實例化。 如果您提供自己的基於 CDI 的javax.enterprise.inject.spi.BeanManager
則可以攔截此工廠的實例化。 CDI 接口(對於 Spring DI 世界是不必要的)冗長,但實現 Spring BeanFactory 支持的 CDI Bean 管理器並不困難。
@Component
public class SpringCdiBeanManager implements BeanManager {
@Autowired
private BeanFactory beanFactory;
@Override
public <T> AnnotatedType<T> createAnnotatedType(Class<T> type) {
return new SpringBeanType<T>(beanFactory, type);
}
@Override
public <T> InjectionTarget<T> createInjectionTarget(AnnotatedType<T> type) {
return (InjectionTarget<T>) type;
}
...
// have empty implementation for other methods
}
依賴於類型的SpringBeanType<T>
將如下所示:
public class SpringBeanType <T> implements AnnotatedType<T>, InjectionTarget<T>{
private BeanFactory beanFactory;
private Class<T> clazz;
public SpringBeanType(BeanFactory beanFactory, Class<T> clazz) {
this.beanFactory = beanFactory;
this.clazz = clazz;
}
@Override
public T produce(CreationalContext<T> ctx) {
return beanFactory.getBean(clazz);
}
...
// have empty implementation for other methods
}
現在,剩下的唯一事情就是在屬性名稱javax.persistence.bean.manager
下將我們的BeanManager
實現注入到 Hibernate 配置設置中。 可能有很多方法可以做到這一點,讓我只介紹其中一種:
@Configuration
public class HibernateConfig {
@Autowired
private SpringCdiBeanManager beanManager;
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter jpaVendorAdapter = new HibernateJpaVendorAdapter(){
@Override
public Map<String, Object> getJpaPropertyMap(){
Map<String, Object> jpaPropertyMap = super.getJpaPropertyMap();
jpaPropertyMap.put("javax.persistence.bean.manager", beanManager);
return jpaPropertyMap;
}
};
// ...
return jpaVendorAdapter;
}
}
請記住,有兩件事必須是 Spring bean:a) SpringCdiBeanManager
,以便BeanFactory
可以注入/自動裝配到它; b) 您的 EntityListener 類,因此該行return beanFactory.getBean(clazz);
會成功。
2)在 Hibernate 5.3 及更高版本中,Spring bean 的事情要容易得多,正如@AdrianShum 非常正確地指出的那樣。 由於 5.3 Hibernate 使用org.hibernate.resource.beans.container.spi.BeanContainer
概念,並且有 Spring Beans 的現成實現, org.springframework.orm.hibernate5.SpringBeanContainer
。 在這種情況下,只需遵循其javadoc 。
由於 Hibernate 5.3 版和 Spring 5.1 版(即 Spring Boot 2.1 版),有一個簡單的解決方案。 沒有 hack,不需要使用 AOP,沒有幫助類,沒有顯式的自動裝配,沒有 init 塊來強制注入。
你只需要:
@Component
並聲明自動裝配的 bean。這是(在 Kotlin 中)如何...
@Component
class EntityXyzListener(val mySpringBean: MySpringBean) {
@PostLoad
fun afterLoad(entityXyz: EntityXyz) {
// Injected bean is available here. (In my case the bean is a
// domain service that I make available to the entity.)
entityXyz.mySpringBean= mySpringBean
}
}
在您的應用程序中訪問LocalContainerEntityManagerFactoryBean
。 然后將以下鍵值對添加到jpaPropertyMap
: AvailableSettings.BEAN_CONTAINER
=> 應用程序上下文的 bean 工廠。
在我的 Spring Boot 應用程序中,我已經有了下面的代碼來配置數據源(例如在這里找到的樣板代碼)。 我只需要添加的代碼行放BEAN_CONTAINER
在屬性jpaPropertyMap
。
@Resource
lateinit var context: AbstractApplicationContext
@Primary
@Bean
@Qualifier("appDatasource")
@ConfigurationProperties(prefix = "spring.datasource")
fun myAppDatasource(): DataSource {
return DataSourceBuilder.create().build()
}
@Primary
@Bean(name = ["myAppEntityManagerFactory"])
fun entityManagerFactoryBean(builder: EntityManagerFactoryBuilder): LocalContainerEntityManagerFactoryBean {
val localContainerEntityManagerFactoryBean =
builder
.dataSource(myAppDatasource())
.packages("com.mydomain.myapp")
.persistenceUnit("myAppPersistenceUnit")
.build()
// the line below does the trick
localContainerEntityManagerFactoryBean.jpaPropertyMap.put(
AvailableSettings.BEAN_CONTAINER, SpringBeanContainer(context.beanFactory))
return localContainerEntityManagerFactoryBean
}
我試圖將Spring依賴項注入到JPA EntityListener中。 這是我的偵聽器類:
@Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true)
public class PliListener {
@Autowired
private EvenementPliRepository evenementPliRepository;
@PostPersist
void onPostPersist(Pli pli) {
EvenementPli ev = new EvenementPli();
ev.setPli(pli);
ev.setDateCreation(new Date());
ev.setType(TypeEvenement.creation);
ev.setMessage("Création d'un pli");
System.out.println("evenementPliRepository: " + evenementPliRepository);
evenementPliRepository.save(ev);
}
}
這是我的Entity類:
@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
但是,我的依賴項(即evenementPliRepository
)始終為null 。
誰能幫忙嗎?
基於 Paulo Merson 的回答,這里是如何使用JpaBaseConfiguration
設置 SpringBeanContainer 的變體。 這是兩個步驟:
第 1 步:將偵聽器定義為 Spring 組件。 請注意,自動裝配通過構造函數注入工作。
@Component
public class PliListener {
private EvenementPliRepository evenementPliRepository;
public PliListener(EvenementPliRepository repo) {
this.evenementPliRepository = repo;
}
@PrePersist
public void touchForCreate(Object target) {
// ...
}
@PostPersist
void onPostPersist(Object target) {
// ...
}
}
第 2 步:設置SpringBeanContainer
,它可以在偵聽器中啟用自動裝配。 SpringBeanContainer JavaDoc可能值得一看。
@Configuration
public class JpaConfig extends JpaBaseConfiguration {
@Autowired
private ConfigurableListableBeanFactory beanFactory;
protected JpaConfig(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
super(dataSource, properties, jtaTransactionManager);
}
@Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> props = new HashMap<>();
// configure use of SpringBeanContainer
props.put(org.hibernate.cfg.AvailableSettings.BEAN_CONTAINER,
new SpringBeanContainer(beanFactory));
return props;
}
}
正如其他人所指出的,SpringBeanContainer 似乎是將 Spring 連接到 Hibernate 的 ManagedBeanRegistryImpl 的方式,后者負責在 Hibernate 創建它的回調對象時創建 EntityListeners 的實例。 創建 bean 的調用被委托給 SpringBeanContainer,它可以通過構造函數注入和自動裝配來創建 Spring bean。 例如 EntityListener 看起來像
public class MyEntityListener {
@Autowired
private AnotherBean anotherBean;
private MyBean myBean;
public InquiryEntityListener(MyBean myBean) {
this.myBean = myBean;
}
public MyEntityListener() {
}
}
請注意,EntityListener 不需要 @Component 注釋,因為這只會創建一個 Hibernate 不使用的額外實例。
但是,在使用 SpringBeanContainer 時,必須牢記一些重要的限制和注意事項。 在我們的用例中,EntityListener 的實例是在創建 Hibernate EntityManager 期間創建的。 由於這發生在 Spring 生命周期的早期,因此此時許多 bean 並不存在。 這導致了以下發現:
SpringBeanContainer 將僅自動裝配/構造器 bean 依賴項,這些依賴項在創建 EntityListener 時存在。 不存在的構造函數依賴會導致調用默認構造函數。 使用 SpringBeanContainer 時本質上存在競爭條件。
解決此問題的方法是將 DefaultListableBeanFactory 實例注入 EntityListener。 稍后,當調用 EntityListeners 生命周期方法(即 @PostLoad、@PostPersist 等)時,可以將所需 bean 的實例從 BeanFactory 中拉出,因為此時 Spring 已經創建了 bean。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.