Подтвердить что ты не робот

Инъекция зависимостей 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.

Кто-нибудь может помочь?

4b9b3361

Ответ 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 заключается в том, что:

  1. они не управляются spring (так что никаких инъекций)

  2. они (или могут быть) созданы до того, как 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();
}