85

I am trying to inject a Spring dependency into an JPA EntityListener. Here is my listener class:

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


}

Here is my Entity class:

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

However, my dependency (i.e. evenementPliRepository) is always null.

Can anyone please help?

balteo
  • 23,602
  • 63
  • 219
  • 412
  • See also http://stackoverflow.com/questions/8616146/eventlisteners-using-hibernate-4-0-with-spring-3-1-0-release – Ralph Jan 29 '15 at 13:29
  • I was also facing the same problem and found a solution and have answered it on another post http://stackoverflow.com/questions/22171221/how-to-inject-entitymanager-in-entitylisteners/42222592#42222592 – Naresh Joshi Feb 14 '17 at 09:45
  • 2
    For anyone interested, I just tried Spring (5.1.9) Boot (2.1.8) and JPA Event Listener integration and it works perfectly fine now, all without any hacks or work arounds. I'm not sure how that is possible, but perhaps it has to do with the Resolver that is used for Validation as well. – Andrew T Finnell May 04 '20 at 01:13
  • @AndrewTFinnell I do constructor inject, it cause a "The dependencies of some of the beans in the application context form a cycle", I do other way it doesn't work. I am pretty sure about the version of spring boot(org.springframework.boot:spring-boot-starter-data-jpa:2.2.6.RELEASE) and hibernate(org.hibernate:hibernate-core:5.4.12.Final) version – Ben Jul 23 '20 at 03:36

16 Answers16

47

A hack to inject dependencies on stateless beans, is to define the dependency as "static", create a setter method so that Spring can inject the dependency (assigning it to the static dependency).

Declare the dependency as static.

static private EvenementPliRepository evenementPliRepository;

Create a method so that Spring can inject it.

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

More details at: http://blog-en.lineofsightnet.com/2012/08/dependency-injection-on-stateless-beans.html

Juan Jimenez
  • 723
  • 4
  • 4
  • 4
    The listener class must be annotated with `@Component` (or similar) for this to work – 1615903 Jul 20 '18 at 08:16
  • tried this solution to inject entity B's repository into an entity A's entity listener class. However, when entity B's repository method (e.g. `findAll()`) is called, below exception is returned: `java.util.ConcurrentModificationException: null at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1043) at java.base/java.util.ArrayList$Itr.next(ArrayList.java:997) at at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:281) ` – Yu Tian Toby May 05 '22 at 09:59
31

This is actually an old question but I found an alternative solution :

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

Probably not the best one but better than static variables w/o AOP + weaving.

Ludovic Guillaume
  • 3,237
  • 1
  • 24
  • 41
  • And how to attach that listener to actual entity without using xml?? – Antoniossss Jul 07 '17 at 11:59
  • 2
    Add `@EntityListeners(MyEntityListener.class)` on the related entity. – Ludovic Guillaume Jul 10 '17 at 09:59
  • Cannot do - model is in separate JAR. What I meant is to how to do that without XML on runtime. – Antoniossss Jul 10 '17 at 10:11
  • This is already a XML-less solution. You should include this to your jar that contains entities. Or maybe create a shared jar. If you don't have access to model's sources, you probably have to extend each class you want to listen. – Ludovic Guillaume Jul 10 '17 at 10:17
  • 2
    Tried this but did not work for me. See the debug message from "processInjectionBasedOnCurrentContext()" method: Current WebApplicationContext is not available for processing of MyEntityListener: Make sure this class gets constructed in a Spring web application. Proceeding without injection. – Naymesh Mistry Aug 10 '17 at 04:49
  • 2
    It worked like a charm! Just a bit worried about possible performance issues since as the method name says it process the injections again – Leonardo Beal Nov 16 '17 at 16:15
  • I think there's another solution which is based on `@Configurable` but I don't have tried yet. – Ludovic Guillaume Nov 16 '17 at 17:57
  • 3
    The part ` SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(this);` should not be executed again and again. It would be better to wrap the call into an test using a instance boolean to check if the DI already was executed. – k_o_ Apr 24 '19 at 23:07
  • 1
    this is a good solution, the only change I had make is call processInjectionBasedOnCurrentContext once in the constructor. – Jachk E Jul 30 '19 at 13:00
  • You know in which case `insert` still always trigger `PostUpdate` and never `PostPersist`? – HoaPhan Apr 16 '20 at 20:28
  • Did not work for me either. Getting the same error as someone mentioned above: "Current WebApplicationContext is not available for processing of MyEntityListener: Make sure this class gets constructed in a Spring web application. Proceeding without injection." – Muhammad Abu Bakr Oct 13 '22 at 11:20
24

I annotated the listener with @Component annotation, then created a non static setter to assign the injected Spring bean, it works well

My code looks like :

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


}
othmane
  • 257
  • 2
  • 4
  • 5
    maybe EntityListener.service=service would be better than this.service=service – othmane Jan 02 '19 at 13:03
  • 3
    it works for me. but can you please explain more. I am not able to understand why using static dependency it worked. – djavaphp Feb 05 '20 at 06:44
  • 3
    The reason is that JPA creates the instance without doing dependency injection, then later Spring creates an instance on its own and sets the autowired dependency, which sets the static variable, enabling all instances to use the MyService instance. – Patrick Cornelissen May 28 '20 at 09:53
  • 1
    This solution is hack, which leaded to need of adding @DirtiesContext to my tests. This happens probably because the listener is using different instance of MyService then rest of the spring context. EclipseAce's solution works perfectly fine. (https://stackoverflow.com/a/60968523/2521628) – Jan Cizmar Apr 24 '21 at 06:35
  • It works, although I do not like to have this static field – Enrico Giurin Sep 14 '22 at 13:01
19

Since Spring V5.1 (and Hibernate V5.3) it should work out of the box as Spring registers as the provider of those classes. see documentation of SpringBeanContainer

D.C
  • 375
  • 2
  • 10
  • In my opinion, it is most natural way for newer Hibernate versions, this answer is the best and has to be accepted one, as Hibernate 5.3+ becomes more and more widely used. @drenda, pls. check out your Hibernate version, it's Hibernate version that matters, not Spring Boot one. In a case you have your Hibernate version overwritten and you happen to use older version, check out my answer. – igor.zh May 12 '20 at 23:53
  • 18
    But what is the solution? You mean the listener with `@Configurable` and the `@Autowired` attribute as posted on the question should work with Hibernate 5.3+? Please clarify. – Paulo Merson Mar 08 '21 at 22:03
  • On Hibernate 6.x (and Spring Boot 3.x) i had to set `hibernate.resource.beans.container` property manually as in provided link and `@Autowired` started working as expected in listener as in question. – marioosh Mar 06 '23 at 10:03
16

And what about this 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;

}

Then the Listener...

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

And the helper...

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

    }

Works for me.

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

chuckedw
  • 648
  • 2
  • 9
  • 13
  • There is a gotcha with this solution if the EntityListener is used in the initialization of ApplicationContext itself; then you must make sure that the `AutowireHelper` bean is initialized before the code that uses it. – holmis83 Jun 30 '17 at 15:37
  • Works, but only after you register the AutowireHelper in the config xml or Java Spring configuration: @Bean public AutowireHelper autowireHelper(){ return AutowireHelper.getInstance(); } – ashes Jan 17 '18 at 09:14
  • what about @EntityListeners(org.springframework.data.jpa.domain.support.AuditingEntityListener.class) – Rajat Jul 15 '20 at 06:41
15

try use ObjectFactory like this

@Configurable
public class YourEntityListener {
    @Autowired
    private ObjectFactory<YourBean> yourBeanProvider;

    @PrePersist
    public void beforePersist(Object target) {
       YourBean yourBean = yourBeanProvider.getObject();
       // do somthing with yourBean here
    }
}

I found this solution in org.springframework.data.jpa.domain.support.AuditingEntityListener from spring-data-jpa.

demo: https://github.com/eclipseAce/inject-into-entity-listener

eclipseAce
  • 159
  • 1
  • 4
14

I started to go down the path of using AOP to inject a spring bean into an Entity listener. After a day and a half of research and trying different things I came across this link which stated:

It is not possible to inject spring managed beans into a JPA EntityListener class. This is because the JPA listener mechanism should be based on a stateless class, so the methods are effectively static, and non-context aware. ... No amount of AOP will save you, nothing gets injected to the ‘object’ representing the listener, because the implementations don’t actually create instances, but uses the class method.

At this point I regrouped and stumbled across the EclipseLink DescriptorEventAdapter. Using this information I created a listener class that extended the Descriptor Adapter.

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

In order to use the class I could have used the @EntityListeners annotation on my entity class. Unfortunately, this method would not allow Spring to control the creation of my listener and as a result would not allow for dependency injection. Instead I added the following 'init' function to my class:

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

Add a little Spring XML configuration

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

Now we have a situation where Spring creates a entity listener, injects it with whatever dependencies are needed, and the listener object registers itself with the entity class to which it intends to listen.

I hope this helps.

jhadley
  • 422
  • 6
  • 18
8

I tested out the approach suggested in https://guylabs.ch/2014/02/22/autowiring-pring-beans-in-hibernate-jpa-entity-listeners/ and worked. Not very clean but does the job. Slightly modified AutowireHelper class for me looked like this:

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

Then called this from entity listener like this:

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;
    }
}
Naymesh Mistry
  • 926
  • 2
  • 12
  • 23
6

The problem with JPA Listeners is that:

  1. they are not managed by Spring (so no injections)

  2. they are (or might be) created before Spring's Application Context is ready (so we can't inject beans on a constructor call)

My workaround to deal with the issue:

1) Create Listener class with public static LISTENERS field:

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) All JPA listeners that we want to be added to Listener.LISTENERS has to extend this class:

public class MyListener extends Listener {

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

    ...
}

3) Now we can get all listeners and inject beans just after Spring's Application Context is ready

@Component
public class ListenerInjector {

    @Autowired
    private ApplicationContext context;

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

}
Taras Shpek
  • 101
  • 2
  • 4
4

I believe it is because this listener bean is not under control of Spring. Spring is not instantiating it, how can Spring know how to find that bean and do the injection?

I haven't tried on that, but seems that you can make use of AspectJ Weaver with Spring's Configurable annotation to have Spring control non-Spring-instantiated beans.

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

Adrian Shum
  • 38,812
  • 10
  • 83
  • 131
  • Hello and thank you Adrian. I have added `` to my configuration. I have aop*.jars in my POM. I don't think I am missing anything. Still `evenementPliRepository` is **null**. Any other idea of what I could be missing?? – balteo Aug 28 '12 at 09:39
  • Do you have the agent started? quoted from the ref: `Further, in certain environments, this support enables load-time weaving without making any modifications to the application server's launch script that will be needed to add -javaagent:path/to/aspectjweaver.jar or (as we describe later in this section) -javaagent:path/to/org.springframework.instrument-{version}.jar (previously named spring-agent.jar).` But to be honest, I haven't really tried this feature before, probably there is other restrictions on this which I am not aware – Adrian Shum Aug 28 '12 at 09:44
  • I am going to check that. I am also realizing that because I use Spring Roo, `@Configurable` is already set up by Roo. What also strikes me is that I get no warning about dependency issues... – balteo Aug 28 '12 at 09:58
  • You can never get error on that, because, as I said, that object instance is not managed by Spring. Having those DI-related annotations means nothing. No one is going to look at those annotations to do anything. – Adrian Shum Aug 29 '12 at 01:30
4

Since version 5.3 of Hibernate and version 5.1 of Spring (that's version 2.1 of Spring Boot), there's an easy solution. No hack, no need to use AOP, no helper classes, no explicit autowiring, no init block to force injection.

You just need to:

  1. Make the listener a @Component and declare the autowired bean, as usual.
  2. Configure JPA in your Spring application to use Spring as the bean provider.

Here's how (in Kotlin)...

1) Entity listener

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

}

2) JPA datasource config

Get access to LocalContainerEntityManagerFactoryBean in your application. Then add to jpaPropertyMap the following key-value pair: AvailableSettings.BEAN_CONTAINER => the application context's bean factory.

In my Spring Boot application I already had the code below to configure a datasource (boilerplate code found here for example). I only had to add the line of code that puts the BEAN_CONTAINER property in the 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
}
Paulo Merson
  • 13,270
  • 8
  • 79
  • 72
3

Another option:

Create a service to make 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);
    }

    

}

Use it:

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

This class is created as a listener from 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;
...

Since the listener is not under Spring's control, it can not access the context bean. I have tried multiple options (@Configurable (...)) and none has worked except to create a class that static access to the context. Already in that dilemma I think that this is an elegant option.

leon cio
  • 366
  • 4
  • 11
3

Building on the answer of Paulo Merson, here is a variation of how to set the SpringBeanContainer by utilizing JpaBaseConfiguration. Here are both steps:

Step 1: Define the listener as a Spring component. Note that autowiring works through constructor injection.

@Component
public class PliListener {

    private EvenementPliRepository evenementPliRepository;

    public PliListener(EvenementPliRepository repo) {
        this.evenementPliRepository = repo;
    }

    @PrePersist
    public void touchForCreate(Object target) {
        // ...
    }

    @PostPersist
    void onPostPersist(Object target) {
        // ...
    }
}

Step 2: Set the SpringBeanContainer, which enables autowiring in the listener. SpringBeanContainer JavaDoc might be worth a look.

@Configuration
public class JpaConfig extends JpaBaseConfiguration {
    
    @Autowired
    private ConfigurableListableBeanFactory beanFactory;

    protected JpaConfig(DataSource dataSource, JpaProperties properties,
            ObjectProvider<JtaTransactionManager> jtaTransactionManager) {
        super(dataSource, properties, jtaTransactionManager);
    }

    @Override
    protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Override
    protected Map<String, Object> getVendorProperties() {
        Map<String, Object> props = new HashMap<>();

        // configure use of SpringBeanContainer
        props.put(org.hibernate.cfg.AvailableSettings.BEAN_CONTAINER, 
            new SpringBeanContainer(beanFactory));
        return props;
    }

}
Markus Pscheidt
  • 6,853
  • 5
  • 55
  • 76
3

@Lazy should do the trick

@Component
public class MyEntityListener {

    @Lazy
    @Autowired
    private ApplicationEventPublisher publisher;

}
cnmuc
  • 6,025
  • 2
  • 24
  • 29
1

The most natural way is, in my opinion, to intervene into the process of instantiating of EntityListener. This way significantly differs in Hibernate pre-5.3 versions and post-5.3 ones.

1) In Hibernate versions earlier than 5.3 org.hibernate.jpa.event.spi.jpa.ListenerFactory is responsible for EntityListener instantiation. The instantiation of this factory can be intercepted if you provide your own CDI-based javax.enterprise.inject.spi.BeanManager. The CDI interfaces are (unnecessary for Spring DI world) verbose, but it's not difficult to implement Spring BeanFactory-backed CDI Bean manager.

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

and the implementation of type-dependent SpringBeanType<T> will look like this:

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 
}

Now, the only thing left is to inject into Hibernate Configuration Settings our implementation of BeanManager under a property name javax.persistence.bean.manager. There are, probably, many ways to do so, let me bring just one of them:

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

Just remember that two things have to be Spring beans: a) SpringCdiBeanManager, so that BeanFactory could be injected/autowired to it; b) your EntityListener class, so that line return beanFactory.getBean(clazz); will be successful.

2) In Hibernate versions 5.3 and later things are much easier for Spring beans, as @AdrianShum very correctly pointed out. Since 5.3 Hibernate uses org.hibernate.resource.beans.container.spi.BeanContainer concept and there is its ready-to-use implementation for Spring Beans, org.springframework.orm.hibernate5.SpringBeanContainer. In this case, just follow its javadoc.

igor.zh
  • 1,410
  • 15
  • 19
0

As others have pointed out, it appears SpringBeanContainer is the way to wire up Spring to Hibernate's ManagedBeanRegistryImpl, which is responsible for creating instances of EntityListeners when Hibernate is creating it's callback objects. Calls to create beans are delegated to SpringBeanContainer which can create Spring beans with both constructor injection and autowiring. For example a EntityListener would look like

public class MyEntityListener {

    @Autowired
    private AnotherBean anotherBean;

    private MyBean myBean;
    public InquiryEntityListener(MyBean myBean) {
        this.myBean = myBean;
    }

    public MyEntityListener() {
    }
}

Note that the EntityListener does NOT require @Component annotation as this only creates an extra instance which is not used by Hibernate.

However when using SpringBeanContainer there are some important limitations and caveats that must be kept in mind. In our use case, instances of our EntityListener were created during the creation of Hibernate EntityManager. As this happened fairly early during the Spring lifecycle, many beans did not exist at this time. This led to the following discovery:

The SpringBeanContainer will only autowire/constructor bean dependencies that exist at the time when the EntityListener is created. Constructor dependencies that don't exist will cause the default constructor to be called. Essentially there is a race condition when using SpringBeanContainer.

The work around for this is to inject a DefaultListableBeanFactory instance into the EntityListener. Later when the EntityListeners lifecycle methods are called (i.e. @PostLoad, @PostPersist, etc.) instances of the desired bean can be pulled out of the BeanFactory as the beans would've been created by Spring at this point.

James W
  • 81
  • 1
  • 2