【发布时间】:2013-10-11 15:19:36
【问题描述】:
在某些情况下,我们需要在 ApplicationListener 内的 Spring 应用程序中写入数据库,因此我们需要在侦听器中使用 @Transactional-annotation 进行事务处理。这些侦听器是从抽象基类扩展而来的,所以普通的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,其中包含两次相同的侦听器(相同的实例:[com.example.TestEventListenerImpl@3aa6d0a4, com.example.TestEventListenerImpl@3aa6d0a4] ),如果听众是ScopedProxyMode.TARGET_CLASS。
现在,我可以通过将代码处理数据库写入一个单独的类并将@Transactional 放在那里来解决这个问题,但我的问题是,这是 Spring 中的错误还是预期的行为?是否有任何其他解决方法,因此即使是最简单的情况,我们也不需要创建单独的服务类(即在侦听器中处理事务,但不会两次获得相同的事件)?
下面是一个显示问题的小例子。
TestEventListenerImpl中带有@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),输出如下:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
Got event com.example.TestEvent[source=Main]
从 TestEventListenerImpl 中删除 @Scope(proxyMode=ScopedProxyMode.TARGET_CLASS),输出为:
Event com.example.TestEvent[source=Main] created by Main
Got event com.example.TestEvent[source=Main]
所以似乎 TARGET_CLASS 范围的 bean 被两次插入到侦听器列表中。
例子:
applicationContext.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"));
}
}
【问题讨论】:
-
是同一个Listener实例吗?顺便问下好问题!
-
我运行了这个,我只得到一个事件。
-
另外,您的示例代码中没有任何代理。
-
@SotiriosDelimanolis:您使用的是哪个 Spring 版本?至少这发生在使用 Maven 的 3.1.3.RELEASE 时。不记得CGlib版本,但我怀疑它会有所不同......我现在在家(在工作中写了这个问题),由于搬到一个新地方,目前只有一台非常旧的迷你笔记本电脑可以使用和翻新,所以现在不能用不同的版本测试它......希望他们已经在新版本中修复了它(如果它确实是一个 Spring 错误,就像我怀疑的那样)。
-
我在
3.2.4。也许重做你想展示给我们的例子,因为上面不会创建任何代理,它没有理由。