Pranav T
Pranav T

Reputation: 156

Event Listeners in spring is called twice

I am an issue with Spring Event Listeners In my Web app, Any immediate help will be appreciated.

Event Listeners is registered and called twice, If I have cyclic dependency.

I have service class, this has @transaction annotation on another methods

@Service(PBSTaskService.BEAN_NAME)
public class PBSTaskServiceImpl extends StandardServiceImpl<ITask> implements          PBSTaskService,ApplicationListener<SurveyDefinitionPublishedEvent>
{
    @Autowired
    private AutoSelectTaskSliceRouteSyncService autoSelectTaskSliceRouteSyncService; //  CYCLIC Dependency
    @Override
    public void onApplicationEvent(SurveyDefinitionPublishedEvent event)
     { 
      System.out.println("PBSTSImpl"); // THIS IS CALLED TWICE
     }
... Other method with @Transaction Annotation
}


@Service(AutoSelectTaskSliceRouteSyncService.BEAN_NAME)
public class AutoSelectTaskSliceRouteSyncServiceImpl implements AutoSelectTaskSliceRouteSyncService
{ 
      @Autowired private PBSTaskService pbsTaskService; // CYCLIC dependency
}

Now If I remove AutoSelectTaskSliceRouteSyncService dependency from First Class, OnApplicationEvent is called once, else twice.

I debugged and found out that SimpleApplicationEventMulticaster.getApplicationListeners(myEvent) : Has two proxy object, one wrapped with Cglib and another default one. But it has two only in case if it has cyclic dependency. If I remove Cyclic dependency, it has only one proxy object and that one is enahnces by CGLIB. my Tx annotation : I had tried it with proxy-target-class="true or false" but no luck.

You may want to have a look on

https://jira.springsource.org/browse/SPR-7940?focusedCommentId=98988&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-98988

Upvotes: 11

Views: 10114

Answers (5)

Marian Kl&#252;hspies
Marian Kl&#252;hspies

Reputation: 17637

I was running into the same issue with one of my services, created another listner with the same event that was only called once.

So what @SimonH wrote is not always the case, only in some circumstances I could not reproduce:

In Spring classes anotated with @Service or @Component which implement the ApplicationListener interface are going to receive duplicate events.

In my case this lead to a double call of the onApplicationEvent method.

@Service
public class TestClass implements ApplicationListener<MyEvent>{

    @Override
    public void onApplicationEvent(MyEvent event){
        // called twice
    }
}

Instead of the code above, I could solve it by creating the Event Listener as an inner class and then call the event method of the parent.

@Service
public class TestClass {

    @Component
    private class MyListener implements ApplicationListener<MyEvent> {

        @Override
        public void onApplicationEvent(MyEvent event) {
            TestClass.this.onApplicationEvent(event);
        }
    }

    public void onApplicationEvent(MyEvent event){
        //Have fun with a single event here
    }
}

Upvotes: 0

igor.zh
igor.zh

Reputation: 2348

In a case of circular dependency between Spring beans, Spring Beans machinery might (under certain circumstances) place two versions of a same bean, the bean itself and its Advised wrapper into the list of ApplicationListeners handled by an ApplicationEventMulticaster. You could, however, implement your custom ApplicationEventMulticaster and fix this bug (it looks like a bug to me).

In a snippet below a custom implementation subclasses Spring's SimpleApplicationEventMulticaster, ignores non-Advised duplicate of a bean, and leaves Advised version of it in the list of ApplicationListeners (most likely you would want an Advised version of your onApplicationEvent method to be called - in a case it is annotated with @Transactional or AOP-advised, but if you need otherwise, the change of algorithm is trivial)

@Component
public class AdviceAwareApplicationEventMulticaster extends SimpleApplicationEventMulticaster {

    @Override
    protected Collection<ApplicationListener<?>> getApplicationListeners(ApplicationEvent event, ResolvableType eventType) {
        Map<ApplicationListener<?>, ApplicationListener<?>> listenersByNakedInstances = new LinkedHashMap<>();// because superclass returns sorted listeners
        Collection<ApplicationListener<?>> applicationListeners = super.getApplicationListeners(event, eventType);
        for (ApplicationListener<?> listener : applicationListeners) {
            boolean advised = false;
            ApplicationListener<?> nakedListener = null;
            if (listener instanceof Advised) {
                try {
                    nakedListener = (ApplicationListener<?>) ((Advised)listener).getTargetSource().getTarget();
                } catch (Exception e) {
                    // TODO 
                }
                advised = true;
            } else
                nakedListener = listener;
            if (advised || !listenersByNakedInstances.containsKey(nakedListener))
                listenersByNakedInstances.put(nakedListener, listener);
        }
        return listenersByNakedInstances.values();
    }
}

You don't need to anyhow make your custom implementation known to Spring, it's enough to have it as a Spring bean and Spring Application Context will pick it up.

Also, don't forget that if there are more one Spring Application Contexts in the application, your Listener might be called for each of those, but it's altogether different story.

Upvotes: 1

rougou
rougou

Reputation: 1216

Since Spring 4.2 you can do away with implementing ApplicationListener and use the new @EventListener annotation on methods in any managed bean. This should help you avoid any conflicts.

Below is an example from https://spring.io/blog/2015/02/11/better-application-events-in-spring-framework-4-2

@Component
public class MyListener {

    @EventListener
    public void handleContextRefresh(ContextRefreshedEvent event) {
        ...
    }
}

Upvotes: 7

SimonH
SimonH

Reputation: 160

In Spring classes anotated with @Service or @Component which implement the ApplicationListener interface are going to receive duplicate events. To resolve the issue, to only receive single events, just remove the @Service or @Compontent annotation.

Upvotes: 3

Pranav T
Pranav T

Reputation: 156

ApplicationEvent Listeners are called twice at many more places in our web app. This is one of scenarios that we caught up.

Reason : Listeners are registered twice. Two proxy are returned wrapped over one instance of listeners. Proxy returned are 1. Dynamic Jdk Interface proxy 2. Cglib Proxy, when we have @transactions annotations.

To recreate these three point are must:

  1. Your listeners must implements ApplicationListener 2. Your listeners must have cyclic dependency with another class 3.Your listeners must have one method annotated with @Transaction.

I have created a separate project where I am able to reproduce it with spring and hibernate. If 2 and 3 are not present together, then we are safe.

Solution I tried many tweaks with spring and transaction configuration but no luck. Then finally with my demo project when I moved the transaction code to another class, so that the listeners do not have any @transaction annotations then it worked for me.

Upvotes: 3

Related Questions