web-dev-qa-db-fra.com

Injection d'une dépendance Spring dans un JPA EntityListener

J'essaie d'injecter une dépendance Spring dans un JPA EntityListener . Voici ma classe d'auditeur:

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


}

Voici ma classe d'entité:

@RooJavaBean
@RooToString
@RooJpaActiveRecord
@EntityListeners(PliListener.class)
public class Pli implements Serializable{
...

Cependant, ma dépendance (c'est-à-dire evenementPliRepository) est toujours nulle .

Quelqu'un peut-il aider s'il vous plait?

31
balteo

Un hack pour injecter des dépendances sur des beans sans état, est de définir la dépendance comme "statique", de créer une méthode de définition afin que Spring puisse injecter la dépendance (en l'attribuant à la dépendance statique).

Déclarez la dépendance comme statique.

static private EvenementPliRepository evenementPliRepository;

Créez une méthode pour que Spring puisse l'injecter.

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

Plus de détails sur: http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html

18
Juan Jimenez

C'est en fait une vieille question mais j'ai trouvé une solution alternative:

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

Probablement pas le meilleur mais meilleur que les variables statiques sans AOP + tissage.

16
Ludovic Guillaume

Et que dire de cette solution?

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

}

Alors l'auditeur ...

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

Et l'assistant ...

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

    }

Travaille pour moi.

Source: http://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/

13
chuckedw

J'ai commencé à utiliser AOP pour injecter un haricot printanier dans un auditeur d'entité. Après une journée et demie de recherche et d’essai, je suis tombé sur ce link qui disait:

Il n'est pas possible d'injecter des haricots gérés par des ressorts dans une classe JPA EntityListener. En effet, le mécanisme d'écoute JPA doit être basé sur une classe sans état, de sorte que les méthodes sont effectivement statiques et non sensibles au contexte. ... Aucune quantité d’AOP ne vous sauvera, rien n’est injecté dans l’objet représentant l’auditeur, car les implémentations ne créent pas réellement d’instances, mais utilisent la méthode de classe.

À ce stade, je me suis regroupé et je suis tombé sur EclipseLink DescriptorEventAdapter . En utilisant ces informations, j'ai créé une classe d'écoute qui a étendu l'adaptateur de descripteur.

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

Pour utiliser la classe, j'aurais pu utiliser l'annotation @EntityListeners sur ma classe d'entité. Malheureusement, cette méthode ne permettrait pas à Spring de contrôler la création de mon auditeur et ne permettrait donc pas l'injection de dépendance. Au lieu de cela, j'ai ajouté la fonction 'init' suivante à ma classe:

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

Ajouter un peu de configuration 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>  

Nous avons maintenant une situation où Spring crée un écouteur d'entité, l'injecte avec toutes les dépendances nécessaires et que l'objet écouteur s'enregistre avec la classe d'entité à laquelle il entend écouter.

J'espère que ça aide.

12
jhadley

J'ai testé l'approche proposée dans https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/ et j'ai travaillé. Pas très propre mais fait le travail. La classe AutowireHelper légèrement modifiée pour moi ressemblait à ceci:

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

Puis appelé ceci de listener d'entité comme ceci:

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;
    }
}
6
Naymesh Mistry

J'ai annoté l'auditeur avec @Component annotation, puis créé un séparateur non statique pour attribuer le bean Spring injecté, cela fonctionne bien

Mon code ressemble à: 

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


}
4
othmane

Je crois que c'est parce que ce haricot auditeur n'est pas sous le contrôle du printemps. Le printemps n’est pas instancié, comment le printemps peut-il savoir trouver ce haricot et faire l’injection?

Je n'ai pas essayé cela, mais il semble que vous pouvez utiliser AspectJ Weaver avec l'annotation configurable de Spring pour obtenir des beans non instanciés par Spring.

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

2
Adrian Shum

Le problème des auditeurs JPA est que:

  1. ils ne sont pas gérés par Spring (donc pas d'injections)

  2. ils sont (ou pourraient être) créés avant le contexte d'application de Spring est prêt (nous ne pouvons donc pas injecter de beans sur un appel de constructeur)

Ma solution de contournement pour traiter le problème:

1) Créez la classe Listener avec le champ public statique 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) Tous les écouteurs JPA que nous voulons ajouter à Listener.LISTENERS doivent étendre cette classe:

public class MyListener extends Listener {

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

    ...
}

3) Nous pouvons maintenant obtenir tous les auditeurs et injecter des haricots juste après que le contexte d'application de Spring soit prêt

@Component
public class ListenerInjector {

    @Autowired
    private ApplicationContext context;

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

}
0
Taras Shpek

Une autre option:

Créez un service pour rendre AplicationContext accessible:

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

}

Utilise le:

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

Cette classe est créée en tant qu'auditeur depuis 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;
...

Puisque l'auditeur n'est pas sous le contrôle de Spring, il ne peut pas accéder au bean de contexte. J'ai essayé plusieurs options (@Configurable (...)) et aucune n'a fonctionné sauf pour créer une classe qui donne un accès statique au contexte. Déjà dans ce dilemme, je pense que c'est une option élégante.

0
leon cio