Инъекция зависимостей Spring в EntityListener JPA
Я пытаюсь ввести 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);
}
}
Вот мой класс Entity:
@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...
Однако моя зависимость (т.е. evenementPliRepository
) всегда равна null.
Кто-нибудь может помочь?
Ответы
Ответ 1
Взлом для встраивания зависимостей в stateless beans заключается в том, чтобы определить зависимость как "статический", создать метод 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
Ответ 2
На самом деле это старый вопрос, но я нашел альтернативное решение:
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 +.
Ответ 3
А как насчет этого решения?
@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/
Ответ 4
Я начал спускаться по пути использования АОП, чтобы ввести spring bean в слушатель Entity. После полутора дней исследований и попыток разного рода я столкнулся с этой ссылкой в которой говорилось:
Встраивать spring beans в класс JPA EntityListener невозможно. Это связано с тем, что механизм слушателя JPA должен основываться на классе без сохранения состояния, поэтому методы являются фактически статичными и неконтекстуальными.... Никакое количество АОП не спасет вас, ничто не будет введено в "объект, представляющий слушателя", потому что реализации фактически не создают экземпляры, а используют метод класса.
На этом этапе я перегруппировался и наткнулся на 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();
}
}
}
Добавьте небольшую конфигурацию XML spring
<!-- Define listener object -->
<bean id="entityListener" class="EntityListener " init-method="init">
<property name="injectedValue" value="Hello World"/>
<property name="entityManagerFactory" ref="emf"/>
</bean>
Теперь мы имеем ситуацию, когда spring создает слушателя сущности, вводит его с любыми зависимостями, а объект-слушатель регистрируется с классом сущности, которому он намеревается слушать.
Надеюсь, это поможет.
Ответ 5
Я аннотировал слушателя аннотацией @Component, затем создал нестатический установщик для назначения внедренного компонента Spring, он работает хорошо
Мой код выглядит так:
@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
Я протестировал подход, предложенный в 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;
}
}
Ответ 7
Я считаю, что это потому, что этот слушатель bean не находится под управлением Spring. Spring не создает экземпляр, как Spring знает, как найти bean и делать инъекцию?
Я не пробовал на этом, но, похоже, вы можете использовать AspectJ Weaver с Spring Настраиваемой аннотацией, чтобы иметь Spring контроль не Spring -instantiated beans.
http://static.springsource.org/spring/docs/3.1.2.RELEASE/spring-framework-reference/html/aop.html#aop-using-aspectj
Ответ 8
Другой вариант:
Создайте сервис, чтобы сделать доступным 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, он не может получить доступ к контекстному компоненту. Я попробовал несколько опций (@Configurable (...)), и никто не работал, кроме как создать класс, статический доступ к контексту. Уже в этой дилемме я считаю, что это элегантный вариант.
Ответ 9
Проблема с JPA Listeners заключается в том, что:
-
они не управляются 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) Все слушатели JPA, которых мы хотим добавить в Listener.LISTENERS
, должны расширить этот класс:
public class MyListener extends Listener {
@PrePersist
public void onPersist() {
...
}
...
}
3) Теперь мы можем получить всех слушателей и внедрить bean-компоненты сразу после того, как Spring Application Context будет готов
@Component
public class ListenerInjector {
@Autowired
private ApplicationContext context;
@EventListener(ContextRefreshedEvent.class)
public void contextRefreshed() {
Listener.LISTENERS.forEach(listener -> context.getAutowireCapableBeanFactory().autowireBean(listener));
}
}
Ответ 10
Начиная с Spring V5.1 (и Hibernate V5.3) он должен работать "из коробки", поскольку Spring регистрируется в качестве поставщика этих классов.
см. документацию по SpringBeanContainer
Ответ 11
Я использовал другой способ. Я использовал 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)
Ответ 12
Эта статья спасет вашу жизнь
https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/
Пример сущности:
@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;
}
Пример слушателя:
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);
}
}
И здесь вы уже видите, что у нас есть бин Spring с автопроводкой, который возвращает текущую дату. Этот прослушиватель сущности прослушивает события перед обновлением и перед сохранением и устанавливает соответствующие даты. Существует также статический вызов метода autowire в AutowireHelper. Что ядро внедрения зависимостей в прослушивателях сущности Hibernate:
/**
* 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);
return;
}
}
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) {
AutowireHelper.applicationContext = applicationContext;
}
/**
* @return the singleton instance.
*/
public static AutowireHelper getInstance() {
return INSTANCE;
}
}
Вам просто нужно зарегистрировать этот AutowireHelper в конфигурации XML или Java Spring, и у вас есть полностью работающее внедрение зависимостей Spring в прослушиватели сущностей Hibernate.
@Bean
public AutowireHelper autowireHelper(){
return AutowireHelper.getInstance();
}