在某些情况下,我们需要在 Spring 中的应用程序中写入数据库,因此我们需要在侦听器中使用-annotationApplicationListener
进行事务处理。@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返回 aLinkedList
包含两次相同的监听器(相同[com.example.TestEventListenerImpl@3aa6d0a4, com.example.TestEventListenerImpl@3aa6d0a4]
的实例:),如果监听器是ScopedProxyMode.TARGET_CLASS
.
现在,我可以通过将代码处理数据库写入一个单独的类并将其放在@Transactional
那里来解决这个问题,但我的问题是,这是 Spring 中的错误还是预期的行为?是否有任何其他解决方法,因此即使是最简单的情况,我们也不需要创建单独的服务类(即在侦听器中处理事务,但不会两次获得相同的事件)?
下面是一个显示问题的小例子。
在@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)
从 TestEventListenerImpl 中删除后,输出为:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
因此,似乎 TARGET_CLASS 范围的 bean 两次插入到侦听器列表中。
例子:
应用程序上下文.xml
<?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.TestEvent
public class TestEvent extends ApplicationEvent
{
public TestEvent(Object source)
{
super(source);
System.out.println("Event " + this + " created by " + source);
}
}
com.example.TestEventListener
public interface TestEventListener extends ApplicationListener<TestEvent>
{
@Override
public void onApplicationEvent(TestEvent event);
}
com.example.TestEventListenerImpl
@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);
}
}
com.example.ListenerTest
public class ListenerTest
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
SimpleApplicationEventMulticaster eventMulticaster = appContext.getBean(SimpleApplicationEventMulticaster.class);
//This is also needed for the bug to reproduce
TestEventListener listener = appContext.getBean(TestEventListener.class);
eventMulticaster.multicastEvent(new TestEvent("Main"));
}
}