72

我正在尝试将 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

有人可以帮忙吗?

4

14 回答 14

43

在无状态 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

于 2012-09-03T02:27:09.303 回答
29

这实际上是一个老问题,但我找到了一个替代解决方案:

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 + 编织的静态变量更好。

于 2016-11-15T15:35:10.447 回答
22

我用@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() {
       ...
    }


}
于 2019-01-02T12:52:28.847 回答
18

从 Spring V5.1(和 Hibernate V5.3)开始,它应该开箱即用,因为 Spring 注册为这些类的提供者。查看SpringBeanContainer的文档

于 2019-08-27T08:59:47.407 回答
16

那么这个解决方案呢?

@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/

于 2015-10-09T14:57:06.133 回答
14

我开始走下使用 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 创建了一个实体监听器,向它注入所需的任何依赖项,然后监听器对象将自己注册到它打算监听的实体类中。

我希望这有帮助。

于 2013-05-08T18:23:38.320 回答
8

我测试了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;
    }
}
于 2017-08-10T05:30:37.267 回答
7

尝试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
    }
}

我在org.springframework.data.jpa.domain.support.AuditingEntityListenerspring-data-jpa 中找到了这个解决方案。

演示:https ://github.com/eclipseAce/inject-into-entity-listener

于 2020-04-01T10:13:35.267 回答
5

JPA Listeners 的问题在于:

  1. 它们不是由 Spring 管理的(所以没有注入)

  2. 它们是(或可能)Spring 的应用程序上下文准备好之前创建的(因此我们不能在构造函数调用中注入 bean)

我处理该问题的解决方法:

1)使用公共静态字段创建Listener类:LISTENERS

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)我们要添加到的所有 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));
    }

}
于 2018-11-28T22:38:25.290 回答
3

我相信这是因为这个监听器 bean 不受 Spring 的控制。Spring 没有实例化它,Spring 怎么知道如何找到那个 bean 并进行注入?

我还没有尝试过,但似乎您可以使用带有 Spring 的 Configurable 注释的 AspectJ Weaver 来让 Spring 控制非 Spring 实例化的 bean。

http://static.springsource.org/spring/docs/3.1.2.RELEASE/spring-framework-reference/html/aop.html#aop-using-aspectj

于 2012-08-28T09:14:47.180 回答
3

另外的选择:

创建一个服务以使 AplicationContext 可访问:

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 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 (...)),除了创建一个静态访问上下文的类之外,没有一个有效。已经陷入困境,我认为这是一个优雅的选择。

于 2018-07-20T10:01:21.673 回答
2

从 Hibernate 5.3 版和 Spring 5.1 版(即 Spring Boot 2.1 版)开始,有一个简单的解决方案。无需 hack,无需使用 AOP,无需辅助类,无需显式自动装配,无需 init 块来强制注入。

你只需要:

  1. 像往常一样,使侦听器 a@Component并声明自动装配的 bean。
  2. 在 Spring 应用程序中配置 JPA 以使用 Spring 作为 bean 提供者。

以下是(在 Kotlin 中)如何...

1) 实体监听器

@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
    }

}

2) JPA 数据源配置

在您的应用程序中访问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
}
于 2021-03-12T15:57:00.910 回答
1

在我看来,最自然的方式是干预 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 
}

现在,唯一剩下的就是将我们BeanManager在属性名下的实现注入到 Hibernate 配置设置中javax.persistence.bean.manager。可能有很多方法可以做到这一点,让我只介绍其中一种:

@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)正如@AdrianShum 非常正确地指出的那样,在Hibernate 5.3 及更高版本中,Spring bean 的事情要容易得多。从 5.3 Hibernate 开始使用org.hibernate.resource.beans.container.spi.BeanContainer概念,并且有其现成的 Spring Beans 实现,org.springframework.orm.hibernate5.SpringBeanContainer. 在这种情况下,只需遵循其javadoc即可。

于 2020-05-12T23:46:09.793 回答
1

基于 Paulo Merson 的回答,这里是如何通过利用设置 SpringBeanContainer 的变体JpaBaseConfiguration。这是两个步骤:

第 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;
    }

}
于 2021-08-19T20:12:25.997 回答