我正在尝试将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
)始终为空。
有人可以帮忙吗?
答案 0 :(得分:24)
为无状态bean注入依赖关系的hack是将依赖项定义为“static”,创建一个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
答案 1 :(得分:18)
这实际上是一个老问题,但我找到了另一种解决方案:
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 +编织的静态变量更好。
答案 2 :(得分: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;
}
然后是听众......
@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/
答案 3 :(得分:12)
我开始沿着使用AOP将spring bean注入Entity监听器的道路走下去。经过一天半的研究和尝试不同的事情,我遇到了这个link,其中说:
无法将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创建一个实体监听器,为它注入所需的任何依赖关系,并且监听器对象将自己注册到它想要监听的实体类。
我希望这会有所帮助。
答案 4 :(得分: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;
}
}
然后从实体监听器中调用它:
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;
}
}
答案 5 :(得分:6)
我用 @Component 注释对侦听器进行注释,然后创建了一个非静态设置器来分配注入的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() {
...
}
}
答案 6 :(得分:2)
我相信这是因为这个监听器bean不受Spring的控制。 Spring没有实例化它,Spring怎么知道如何找到那个bean并进行注入?
我还没有尝试过,但似乎你可以使用带有Spring的可配置注释的AspectJ Weaver来使Spring控件非Spring实例化的bean。
答案 7 :(得分:1)
另一个选择:
创建服务以使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 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(...)),但除了创建一个静态访问上下文的类之外,其他方法都没有起作用。在这种困境中,我认为这是一个不错的选择。
答案 8 :(得分:1)
尝试像这样使用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
答案 9 :(得分: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
下将javax.persistence.bean.manager
的实现注入到Hibernate Configuration Settings中。可能有很多方法可以实现,我只带其中一种:
@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。
答案 10 :(得分:1)
自从 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 应用程序中,我已经有了下面的代码来配置数据源(例如找到的样板代码 here)。我只需要添加将 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
}
答案 11 :(得分:0)
JPA侦听器的问题是:
它们不是由Spring管理的(因此没有注射)
它们是(或可能)在之前创建的。Spring的Application Context准备就绪(因此我们不能在构造函数调用中注入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)我们要添加到Listener.LISTENERS
的所有JPA侦听器都必须扩展此类:
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));
}
}
答案 12 :(得分:0)
从Spring V5.1(和Hibernate V5.3)开始,它应该立即可用,因为Spring注册为这些类的提供者。 请参阅SpringBeanContainer
的文档答案 13 :(得分:0)
我使用了另一种方式。我正在使用Spring MVC 我创建了一个名为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());
}
};
}
现在从AppContextAware中,无论使用什么类,我都可以在任何地方获得上下文
AppContextAware.getInstance().getApplicationContext().getBean(xyz.class)