将Spring依赖项注入JPA EntityListener

bal*_*teo 43 spring dependency-injection jpa spring-roo entitylisteners

我试图将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);
    }


}
Run Code Online (Sandbox Code Playgroud)

这是我的Entity类:

@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
Run Code Online (Sandbox Code Playgroud)

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

有人可以帮忙吗?

Jua*_*nez 30

注入依赖于无状态bean的hack是将依赖项定义为"static",创建一个setter方法,以便Spring可以注入依赖项(将其分配给静态依赖项).

将依赖项声明为static.

static private EvenementPliRepository evenementPliRepository;
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 必须使用`@ Component`(或类似的名称)对侦听器类进行注释,才能使其正常工作 (3认同)

Lud*_*ume 21

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

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 部分`SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);`不应该一次又一次地执行。最好使用实例布尔值将调用包装到测试中,以检查 DI 是否已执行。 (3认同)
  • 在相关实体上添加`@EntityListeners(MyEntityListener.class)`。 (2认同)
  • 尝试过这个但对我不起作用。请参阅“processInjectionBasedOnCurrentContext()”方法中的调试消息:当前 WebApplicationContext 不可用于处理 MyEntityListener:确保在 Spring Web 应用程序中构造此类。无需注射即可继续。 (2认同)
  • 它就像一个魅力!只是有点担心可能的性能问题,因为方法名称说它再次处理注入 (2认同)

chu*_*edw 14

那个解决方案怎么样?

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

}
Run Code Online (Sandbox Code Playgroud)

那么听众......

@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);
    }
}
Run Code Online (Sandbox Code Playgroud)

帮帮人......

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

    }
Run Code Online (Sandbox Code Playgroud)

适合我.

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


小智 14

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

  • 但解决办法是什么?您的意思是问题上发布的具有“@Configurable”和“@Autowired”属性的侦听器应该与 Hibernate 5.3+ 配合使用?请澄清。 (5认同)

jha*_*ley 13

我开始沿着使用AOP将spring bean注入Entity监听器的道路走下去.经过一天半的研究和尝试不同的事情,我遇到了这个链接,其中说:

无法将spring托管bean注入JPA EntityListener类.这是因为JPA侦听器机制应该基于无状态类,因此这些方法实际上是静态的,并且非上下文感知....没有任何AOP会保存你,没有任何东西被注入到代表监听器的'对象'中,因为实现实际上并不创建实例,而是使用类方法.

此时,我重新组合并偶然发现了EclipseLink DescriptorEventAdapter.使用这些信息,我创建了一个扩展Descriptor Adapter的监听器类.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

添加一点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>  
Run Code Online (Sandbox Code Playgroud)

现在我们有一种情况,Spring创建一个实体监听器,为它注入所需的任何依赖关系,并且监听器对象将自己注册到它想要监听的实体类.

我希望这有帮助.


oth*_*ane 9

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


}
Run Code Online (Sandbox Code Playgroud)

  • 原因是JPA创建实例时没有进行依赖注入,然后Spring自己创建一个实例并设置自动装配依赖,这会设置静态变量,使所有实例都可以使用MyService实例。 (3认同)
  • 这个解决方案是 hack,这导致需要将 @DirtiesContext 添加到我的测试中。发生这种情况可能是因为侦听器使用的是 MyService 的不同实例,然后是 spring 上下文的其余部分。EclipseAce 的解决方案工作得很好。(/sf/answers/4267796641/) (3认同)
  • 也许EntityListener.service = service会比this.service = service更好 (2认同)
  • 这个对我有用。但你能解释一下吗?我无法理解为什么使用静态依赖它有效。 (2认同)

Nay*_*try 6

我测试了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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后从实体监听器中调用它,如下所示:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Tar*_*pek 5

JPA 侦听器的问题在于:

  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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public class MyListener extends Listener {

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

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)


ecl*_*Ace 5

尝试使用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
    }
}
Run Code Online (Sandbox Code Playgroud)

org.springframework.data.jpa.domain.support.AuditingEntityListener从 spring-data-jpa 中找到了这个解决方案。

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


Mar*_*idt 5

基于 Paulo Merson 的答案,这里是如何使用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) {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)


cnm*_*muc 5

@Lazy 应该可以解决问题

@Component
public class MyEntityListener {

    @Lazy
    @Autowired
    private ApplicationEventPublisher publisher;

}
Run Code Online (Sandbox Code Playgroud)


Adr*_*hum 4

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

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

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