将 Spring 依赖项注入 JPA EntityListener

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/12155632/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-08 05:20:49  来源:igfitidea点击:

Injecting a Spring dependency into a JPA EntityListener

springjpadependency-injectionspring-rooentitylisteners

提问by balteo

I am trying to inject a Spring dependencyinto an JPA EntityListener. Here is my listener class:

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


}

Here is my Entity class:

这是我的实体类:

@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...

However, my dependency (i.e. evenementPliRepository) is always null.

但是,我的依赖项(即evenementPliRepository始终为 null

Can anyone please help?

有人可以帮忙吗?

回答by Juan Jimenez

A hack to inject dependencies on stateless beans, is to define the dependency as "static", create a setter method so that Spring can inject the dependency (assigning it to the static dependency).

在无状态 bean 上注入依赖项的一个技巧是将依赖项定义为“静态”,创建一个 setter 方法,以便 Spring 可以注入依赖项(将其分配给静态依赖项)。

Declare the dependency as static.

将依赖声明为静态。

static private EvenementPliRepository evenementPliRepository;

Create a method so that Spring can inject it.

创建一个方法,以便 Spring 可以注入它。

@Autowired
public void init(EvenementPliRepository evenementPliRepository) 
{
    MyListenerClass.evenementPliRepository = evenementPliRepository;
    logger.info("Initializing with dependency ["+ evenementPliRepository +"]"); 
}

More details at: http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html

更多详细信息,请访问:http: //blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html

回答by Ludovic Guillaume

This is actually an old question but I found an alternative solution :

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

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

Probably not the best one but better than static variables w/o AOP + weaving.

可能不是最好的,但比没有 AOP + 编织的静态变量更好。

回答by chuckedw

And what about this solution?

这个解决方案呢?

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

}

Then the Listener...

那么听者...

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

And the helper...

还有帮手...

    /**
     * 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;
        }

    }

Works for me.

对我来说有效。

Source: http://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/

来源:http: //guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/

回答by jhadley

I started to go down the path of using AOP to inject a spring bean into an Entity listener. After a day and a half of research and trying different things I came across this linkwhich stated:

我开始走上使用 AOP 将 spring bean 注入实体侦听器的路径。经过一天半的研究和尝试不同的东西后,我发现了这个链接,它说:

It is not possible to inject spring managed beans into a JPA EntityListener class. This is because the JPA listener mechanism should be based on a stateless class, so the methods are effectively static, and non-context aware. ... No amount of AOP will save you, nothing gets injected to the ‘object' representing the listener, because the implementations don't actually create instances, but uses the class method.

不可能将 spring 管理的 bean 注入 JPA EntityListener 类。这是因为 JPA 侦听器机制应该基于无状态类,因此这些方法实际上是静态的,并且不受上下文影响。... 再多的 AOP 也拯救不了你,没有任何东西被注入到代表侦听器的“对象”中,因为实现实际上并不创建实例,而是使用类方法。

At this point I regrouped and stumbled across the EclipseLink DescriptorEventAdapter. Using this information I created a listener class that extended the Descriptor Adapter.

在这一点上,我重新组合并偶然发现了 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
    }
}

In order to use the class I could have used the @EntityListeners annotation on my entity class. Unfortunately, this method would not allow Spring to control the creation of my listener and as a result would not allow for dependency injection. Instead I added the following 'init' function to my class:

为了使用该类,我可以在实体类上使用 @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();
        }
    }
}

Add a little Spring XML configuration

添加一点 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>  

Now we have a situation where Spring creates a entity listener, injects it with whatever dependencies are needed, and the listener object registers itself with the entity class to which it intends to listen.

现在我们有这样一种情况,Spring 创建一个实体侦听器,将所需的任何依赖项注入它,然后侦听器对象将自己注册到它打算侦听的实体类。

I hope this helps.

我希望这有帮助。

回答by othmane

I annotated the listener with @Componentannotation, then created a non static setterto assign the injected Spring bean, it works well

我用@Component注解注解了监听器,然后创建了一个非静态的setter来分配注入的Spring bean,效果很好

My code looks like :

我的代码看起来像:

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


}

回答by Naymesh Mistry

I tested out the approach suggested in https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/and worked. Not very clean but does the job. Slightly modified AutowireHelper class for me looked like this:

我测试了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;
    }
}

Then called this from entity listener like this:

然后像这样从实体侦听器调用它:

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

回答by Taras Shpek

The problem with JPA Listeners is that:

JPA 侦听器的问题在于:

  1. they are not managed by Spring (so no injections)

  2. they are (or might be) created beforeSpring's Application Context is ready (so we can't inject beans on a constructor call)

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

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

My workaround to deal with the issue:

我处理这个问题的解决方法:

1) Create Listenerclass with public static LISTENERSfield:

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) All JPA listeners that we want to be added to Listener.LISTENERShas to extend this class:

2) 我们要添加的所有 JPA 侦听器Listener.LISTENERS都必须扩展此类:

public class MyListener extends Listener {

    @PrePersist
    public void onPersist() {
        ...
    }

    ...
}

3) Now we can get all listeners and inject beans just after Spring's Application Context is ready

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

}

回答by Adrian Shum

I believe it is because this listener bean is not under control of Spring. Spring is not instantiating it, how can Spring know how to find that bean and do the injection?

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

I haven't tried on that, but seems that you can make use of AspectJ Weaver with Spring's Configurable annotation to have Spring control non-Spring-instantiated beans.

我还没有尝试过,但似乎您可以使用带有 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

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

回答by D.C

Since Spring V5.1 (and Hibernate V5.3) it should work out of the box as Spring registers as the provider of those classes. see documentation of SpringBeanContainer

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

回答by leon cio

Another option:

另外一个选项:

Create a service to make AplicationContext accessible:

创建一个服务以使 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;
    }

}

Use it:

用它:

...    
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);
    }
}

This class is created as a listener from jpa:

这个类是作为 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;
...

Since the listener is not under Spring's control, it can not access the context bean. I have tried multiple options (@Configurable (...)) and none has worked except to create a class that static access to the context. Already in that dilemma I think that this is an elegant option.

由于侦听器不受 Spring 的控制,因此它无法访问上下文 bean。我尝试了多个选项(@Configurable (...)),除了创建一个静态访问上下文的类之外,没有一个有效。已经陷入困境,我认为这是一个优雅的选择。