使用ScopedProxyMode.TARGET_CLASS时,在侦听器上收到重复事件

时间:2013-10-04 14:54:36

标签: spring listener cglib

在某些情况下,我们需要在ApplicationListener内的Spring -application中写入数据库,因此我们需要使用@Transactional - 注释在侦听器中进行事务处理。这些侦听器是从一个抽象的基类扩展的,所以普通的ScopedProxyMode.INTERFACES不会这样做,因为Spring容器抱怨期望一个抽象类类型的bean,而不是“[$ Proxy123]”。但是,使用Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),侦听器会两次收到相同的事件。我们使用的是Spring 3.1.3.RELEASE版。 (编辑:仍然出现版本3.2.4.RELEASE)

使用调试器挖掘Spring源代码,我发现 org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners 返回一个包含相同侦听器两次的LinkedList(同一个实例:{ {1}}),如果听众是[com.example.TestEventListenerImpl@3aa6d0a4, com.example.TestEventListenerImpl@3aa6d0a4]

现在,我可以通过将代码处理数据库写入一个单独的类并将ScopedProxyMode.TARGET_CLASS放在那里来解决这个问题,但我的问题是,这是Spring中的错误还是预期的行为?有没有其他的解决方法,所以我们不需要创建单独的服务类(即在侦听器中处理事务,但不要两次得到相同的事件),即使是最简单的情况?

下面是一个显示问题的小例子。

在TestEventListenerImpl中使用@Transactional,输出如下:

@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)

从TestEventListenerImpl中删除Event com.example.TestEvent[source=Main] created by Main Got event com.example.TestEvent[source=Main] Got event com.example.TestEvent[source=Main] ,输出为:

@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)

所以似乎TARGET_CLASS -scoped bean两次插入到监听器列表中。

示例:

的applicationContext.xml

Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]

com.example.TestEvent

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <context:component-scan base-package="com.example/**"/>

</beans>

com.example.TestEventListener

public class TestEvent extends ApplicationEvent
{
    public TestEvent(Object source)
    {
        super(source);
        System.out.println("Event " + this + " created by " + source);
    }
}

com.example.TestEventListenerImpl

public interface TestEventListener extends ApplicationListener<TestEvent>
{

    @Override
    public void onApplicationEvent(TestEvent event);

}

com.example.ListenerTest

@Component
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)  //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{
    @Override
    public void onApplicationEvent(TestEvent event)
    {
        System.out.println("Got event " + event);
    }
}

1 个答案:

答案 0 :(得分:2)

如果这是一个错误或预期的行为,我不能说,但这里是脏的:

声明像

这样的bean
@Component
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS)  //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{

创建两个BeanDefinition个实例:

  1. 描述Scoped bean的RootBeanDefinition
  2. 描述实际对象的ScannedGenericBeanDefinition
  3. ApplicationContext将使用这些bean定义来创建两个bean:

    1. 一个ScopedProxyFactoryBean bean。这是一个FactoryBean,它将TestEventListenerImpl对象包装在代理中。
    2. 一个TestEventListenerImpl bean。实际的TestEventListenerImpl对象。
    3. 初始化过程的一部分是注册实现ApplicationListener接口的bean。 TestEventListenerImpl bean是热切地(立即创建)并注册为ApplicationListener

      ScopedProxyFactoryBean是懒惰的,它应该创建的bean(代理)仅在请求时生成。发生这种情况时,它也会被注册为ApplicationListener。只有在明确要求时才能看到这一点

      TestEventListener listener = appContext.getBean(TestEventListener.class);
      

      或隐式使用@Autowired将其注入另一个bean。请注意,实际的目标对象是添加的,而不是代理。