Spring中的事件监听器被调用两次

时间:2014-02-18 11:32:50

标签: spring event-listener

我是Spring Event Listeners的问题在我的Web应用程序中,任何直接的帮助都将受到赞赏。

事件监听器已注册并被调用两次,如果我有循环依赖。

我有服务类,这在另一个方法上有@transaction注释

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

现在如果我从First Class中删除AutoSelectTaskSliceRouteSyncService依赖项,则调用OnApplicationEvent一次,否则调用两次。

我调试并发现了 SimpleApplicationEventMulticaster.getApplicationListeners(myEvent):有两个代理对象,一个用Cglib包装,另一个用默认的。但它只有两个,如果它有循环依赖。如果我删除了Cyclic依赖项,它只有一个代理对象,那一个是CGLIB的enahnces。 我的Tx注释:  我曾尝试使用proxy-target-class =&#34; true或false&#34;但没有运气。

您可能想查看

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

5 个答案:

答案 0 :(得分:2)

ApplicationEvent侦听器在我们的Web应用程序中的更多位置被调用两次。这是我们追上的一个场景。

原因: 听众注册两次。在一个侦听器实例上返回两个代理。返回的代理是1.动态Jdk接口代理2. Cglib代理,当我们有@transactions注释时。

重新创建这三点必须:

  1. 您的侦听器必须实现ApplicationListener 2.您的侦听器必须与另一个类具有循环依赖关系3.您的侦听器必须具有一个使用@Transaction注释的方法。
  2. 我创建了一个单独的项目,我可以使用spring和hibernate重现它。如果2和3不在一起,那么我们是安全的。

    解                我尝试了许多弹簧和事务配置的调整,但没有运气。最后,当我将事务代码移动到另一个类时,我的演示项目,以便侦听器没有任何@transaction注释,然后它适用于我。

答案 1 :(得分:1)

在实现ApplicationListener接口的@Service或@Component中分配的Spring类将接收重复事件。要解决此问题,只需要接收单个事件,只需删除@Service或@Compontent注释。

答案 2 :(得分:0)

从Spring 4.2开始,您可以取消实现ApplicationListener并在任何托管Bean中的方法上使用新的@EventListener批注。这应该可以帮助您避免任何冲突。

以下是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) {
        ...
    }
}

答案 3 :(得分:0)

在Spring bean之间存在循环依赖的情况下,Spring Beans机制可能(在某些情况下)会将同一个bean的两个版本(bean本身及其Advised包装器)放入ApplicationListener处理的ApplicationEventMulticaster列表。 但是,您可以实现自定义ApplicationEventMulticaster并修复此错误(对我来说似乎是个错误)。

在自定义实现下方的摘要中,将Spring的SimpleApplicationEventMulticaster子类化,忽略了非Advised的bean副本,并将Advised的版本保留在ApplicationListener的列表中s(最有可能希望调用Advised方法的onApplicationEvent版本-在使用@Transactional或AOP建议的情况下进行注释的情况,但是如果需要,可以使用算法的改变很小)

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

您无需以任何方式让Spring知道您的自定义实现,就足以让它成为Spring bean,Spring Application Context会选择它。

此外,请不要忘记,如果应用程序中有多个Spring Application Contexts,则可能会为每个监听器调用 ,但这完全是不同的故事。

答案 4 :(得分:0)

我的一个服务遇到了同样的问题,使用相同的事件创建了另一个监听器,但只调用了一次。

所以@SimonH 所写的是并非总是如此,只是在某些情况下我无法重现

<块引用>

在用@Service 或@Component 注释的Spring 类中,实现ApplicationListener 接口的类将接收重复的事件。

就我而言,这会导致对 onApplicationEvent 方法的两次调用。

@Service
public class TestClass implements ApplicationListener<MyEvent>{

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

代替上面的代码,我可以通过将事件侦听器创建为内部类,然后调用父类的事件方法来解决。

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