Spring 使用ScopedProxyMode.TARGET_类时在侦听器上接收到重复事件
在某些情况下,我们需要在Spring 使用ScopedProxyMode.TARGET_类时在侦听器上接收到重复事件,spring,listener,cglib,Spring,Listener,Cglib,在某些情况下,我们需要在ApplicationListener中的Spring应用程序中写入数据库,因此我们需要在侦听器中使用@Transactional-注释进行事务处理。这些监听器是从抽象基类扩展而来的,因此普通的ScopedProxyMode.INTERFACES不会这样做,因为Spring容器抱怨需要的是抽象类类型的bean,而不是“[$Proxy123]”。但是,使用作用域(proxyMode=ScopedProxyMode.TARGET\u CLASS),侦听器会两次接收相同的事件。
ApplicationListener
中的Spring应用程序中写入数据库,因此我们需要在侦听器中使用@Transactional
-注释进行事务处理。这些监听器是从抽象基类扩展而来的,因此普通的ScopedProxyMode.INTERFACES
不会这样做,因为Spring容器抱怨需要的是抽象类类型的bean,而不是“[$Proxy123]”。但是,使用作用域(proxyMode=ScopedProxyMode.TARGET\u CLASS)
,侦听器会两次接收相同的事件。我们使用的是Spring版本3.1.3.0。(编辑:版本3.2.4.RELEASE仍然存在)
通过调试器深入研究Spring源代码,我发现org.springframework.context.event.AbstractApplicationEventMulticaster.getApplicationListeners返回一个LinkedList
,其中包含两次相同的侦听器(同一实例:[com.example。TestEventListenerImpl@3aa6d0a4,com.example。TestEventListenerImpl@3aa6d0a4]
),如果侦听器是ScopedProxyMode.TARGET\u类
现在,我可以通过将代码处理数据库写入到一个单独的类中并将@Transactional
放在那里来解决这个问题,但我的问题是,这是Spring中的一个bug还是预期的行为?是否有其他的解决方法,这样我们就不需要创建单独的服务类了(即在侦听器中处理事务,但不要两次获取同一事件)即使是最简单的情况
下面是一个小例子,说明了这个问题
使用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]
因此,似乎目标类范围的bean被两次插入到侦听器列表中
例如:
applicationContext.xml
com.example.TestEventListener
com.example.ListenerTest
我不知道这是一个bug还是预期的行为,但这里有一个问题: 宣布一个豆状
@Component
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{
创建两个BeanDefinition
实例:
RootBeanDefinition
ScannedGenericBeanDefinition
ApplicationContext
将使用这些bean定义创建两个bean:
ScopedProxy FactoryBean
bean。这是一个将TestEventListenerImpl
对象包装在代理中的FactoryBean
TestEventListenerImpl
bean。实际的TestEventListenerImpl
对象ApplicationListener
接口的bean。TestEventListenerImpl
bean立即创建并注册为ApplicationListener
ScopedProxyFactoryBean
是惰性的,它应该创建的bean(代理)只在被请求时生成。当这种情况发生时,它也会被注册为ApplicationListener
。只有在显式请求时才会看到这一点
TestEventListener listener = appContext.getBean(TestEventListener.class);
或者隐式地使用
@Autowired
将其注入另一个bean。请注意,添加的是实际的目标对象,而不是代理。是侦听器的同一个实例吗?顺便问一下,这是一个很好的问题!我运行了这个程序,只得到了一个事件。此外,在您的示例代码中没有任何代理。@SotiriosDelimanolis:您使用的是哪个Spring版本?至少在Maven的3.1.3.RELEASE中出现了这种情况。我记不起CGlib版本,但我怀疑它会有什么不同……我现在在家(在工作时写下问题),目前只有一台非常旧的迷你笔记本电脑可供使用,因为要搬到一个新的地方进行翻新,所以现在无法用不同的版本进行测试…希望他们在新的版本中修复了它(如果它确实是一个Spring bug,就像我怀疑的那样)。我正在使用3.2.4
。可能需要重新编写您想要向我们展示的示例,因为上面的内容不会创建任何代理,它没有理由创建任何代理。感谢您的深入了解!我必须检查我们的侦听器是否出于任何原因自动连接或从上下文中检索,并查看我是否可以解决此问题,如果这可以阻止代理的l监听器不会被调用两次。这真的不是什么大问题,但我更愿意让监听器在琐碎的情况下直接调用带有@Transactional的DAO接口(比如触发数据库记录更新的事件等)而不是整个侦听器->服务接口->服务实现->DAO-dance,因为它似乎只是不必要的样板文件。@esaj我还不太明白,如果它要成为原型/单例,为什么需要@Scope
。在实际用例中,侦听器是从抽象基类派生的,有些实现是uld需要代理@Transactional注释才能工作。如果不代理,则不会创建任何事务。使用“普通”ScopedProxyMode.INTERFACES,应用程序容器无法启动,抱怨AbstractApplicationEventMulticaster(或类似)希望侦听器bean为抽象类类型,而不是代理(如[$Proxy123]”).使用TARGET_类代理,一切似乎都正常工作,直到我们注意到事件在代理侦听器上触发两次;P@esaj有了正确的CGLIB库,@Component
和@Transactional
,即使没有@Scope
,您也应该会得到一个增强的\u BY\u CGLIB
样式的代理。谢谢你的提示,我已经我在考虑AOP/加载时代码编织/whatsitcalled事务之类的事情,但还没有时间深入研究它。目前我想我们只需要使用侦听器->服务->DAO-在需要的地方路由,因为还有很多其他的事情要做(像往常一样),但是定义
public interface TestEventListener extends ApplicationListener<TestEvent>
{
@Override
public void onApplicationEvent(TestEvent event);
}
@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);
}
}
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"));
}
}
@Component
@Scope(proxyMode=ScopedProxyMode.TARGET_CLASS) //If commented out, the event won't be received twice
public class TestEventListenerImpl implements TestEventListener
{
TestEventListener listener = appContext.getBean(TestEventListener.class);