Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 613
Pranav T Avatar asked Feb 18 '14 11:02

Pranav T


4 Answers

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.

like image 127
Pranav T Avatar answered Oct 20 '22 21:10

Pranav T


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.

like image 34
SimonH Avatar answered Oct 20 '22 22:10

SimonH


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) {
        ...
    }
}
like image 27
rougou Avatar answered Oct 20 '22 22:10

rougou


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.

like image 34
igor.zh Avatar answered Oct 20 '22 21:10

igor.zh