将Spring依赖项注入JPA EntityListener

将Spring依赖项注入JPA EntityListener,spring,jpa,dependency-injection,spring-roo,entitylisteners,Spring,Jpa,Dependency Injection,Spring Roo,Entitylisteners,我正在尝试向JPA EntityListener注入Spring依赖项。下面是我的listener类: @Configurable(autowire = Autowire.BY_TYPE, dependencyCheck = true) public class PliListener { @Autowired private EvenementPliRepository evenementPliRepository; @PostPersist void on

我正在尝试JPA EntityListener注入Spring依赖项。下面是我的listener类:

@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{
...
但是,我的依赖项(即,
evenementpli存储库
始终为空


有人能帮忙吗?

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

我还没有尝试过,但似乎可以使用AspectJ Weaver和Spring的可配置注释来控制非Spring实例化的bean


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

将依赖项声明为静态

static private EvenementPliRepository evenementPliRepository;
创建一个方法,以便Spring可以注入它

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

更多细节见:

我开始使用AOP将Springbean注入实体侦听器。经过一天半的研究和尝试,我发现了这样一句话:

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

在这一点上,我重新组合,偶然发现了日食。使用这些信息,我创建了一个扩展描述符适配器的侦听器类

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();
        }
    }
}
public void init(){
JpaEntityManager entityManager=null;
试一试{
//创建用于此功能的实体管理器
entityManager=(JPAEEntityManager)entityManager工厂。createEntityManager();
//使用实体管理器获取实体类的类描述符
类描述符描述=
entityManager.getSession().getClassDescriptor(.class);
//将此类作为侦听器添加到类描述符
desc.getEventManager().addListener(此);
}最后{
如果(entityManager!=null){
//清理实体管理器
entityManager.close();
}
}
}
添加一点SpringXML配置

<!-- Define listener object -->
<bean id="entityListener" class="EntityListener " init-method="init">
    <property name="injectedValue" value="Hello World"/>
    <property name="entityManagerFactory" ref="emf"/>
</bean>  

现在我们遇到了这样一种情况:Spring创建一个实体侦听器,向它注入所需的任何依赖项,侦听器对象向它想要侦听的实体类注册自己


我希望这有帮助。

这个解决方案怎么样

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

    }
对我有用

资料来源:

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

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));
    }
}
公共类MyEntityListener{
@自动连线
私有应用程序ventpublisher publisher;
@后复印机
public void postPersist(MyEntity目标){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(新OnCreatedEvent(this,target));
}
@假想
公共无效状态更新(MyEntity目标){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(新的onupdatevent(this,target));
}
@移除后
public void postDelete(MyEntity目标){
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);
publisher.publishEvent(新的OnDeletedEvent(this,target));
}
}

可能不是最好的方法,但比不使用AOP+编织的静态变量要好。

我测试了中建议的方法并成功了。不是很干净,但很管用。对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;
    }
}
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 
}
然后从实体侦听器调用此函数,如下所示:

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;
    }
}
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 
}
另一种选择:

创建服务以使应用程序上下文可访问:

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(…)),除了创建一个静态访问上下文的类之外,其他选项都不起作用。已经处于这种困境的我认为这是一个优雅的选择。

JPA听众的问题是:

  • 它们不是由Spring管理的(因此没有注入)

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

  • 我处理此问题的变通方法:

    1) 使用公共静态
    侦听器创建
    侦听器
    字段:

    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);
        }
    }
    
    3) 现在,我们可以在Spring的应用程序上下文准备好之后获取所有侦听器并注入bean

    @Component
    public class ListenerInjector {
    
        @Autowired
        private ApplicationContext context;
    
        @EventListener(ContextRefreshedEvent.class)
        public void contextRefreshed() {
           Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
        }
    
    }
    

    我用@Component注释了侦听器,然后创建了一个非静态setter来分配注入的Springbean,它工作得很好

    我的代码如下所示:

    @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() {
           ...
        }
    
    
    }
    
    自SpringV5.1(和HibernateV5.3)以来,它应该作为Spring注册为这些类的提供者而开箱即用。
    请参阅文档

    我使用了另一种方法。我使用的是SpringMVC 我创建了一个名为AppContextAware的单例类

    public class AppContextAware{
    
    private static AppContextAware appContextAware;
    private ApplicationContext applicationContext;
    private AppContextAware() {
    }
    
    public static AppContextAware getInstance() {
        if(null == appContextAware) {
            appContextAware = new AppContextAware();
        }
        return appContextAware;
    }
    
    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }}
    
    然后,我通过注册一个事件侦听器来等待上下文刷新事件

    @Bean
    public ApplicationListener<ContextRefreshedEvent> applicationListener() {
        final AppContextAware appContextAware = AppContextAware.getInstance();
        return new ApplicationListener<ContextRefreshedEvent>() {
            public void onApplicationEvent(ContextRefreshedEvent event) {
                appContextAware.setApplicationContext(event.getApplicationContext());
            }
        };
    }
    

    试着像这样使用
    ObjectFactory

    @可配置
    公共类YourEntityListener
    
    @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;
        }
    }
    
    @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
        }
    
    }
    
    @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
    }