在某些情况下,我们需要在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);
}
}
答案 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
个实例:
RootBeanDefinition
。 ScannedGenericBeanDefinition
。 ApplicationContext
将使用这些bean定义来创建两个bean:
ScopedProxyFactoryBean
bean。这是一个FactoryBean
,它将TestEventListenerImpl
对象包装在代理中。 TestEventListenerImpl
bean。实际的TestEventListenerImpl
对象。初始化过程的一部分是注册实现ApplicationListener
接口的bean。 TestEventListenerImpl
bean是热切地(立即创建)并注册为ApplicationListener
。
ScopedProxyFactoryBean
是懒惰的,它应该创建的bean(代理)仅在请求时生成。发生这种情况时,它也会被注册为ApplicationListener
。只有在明确要求时才能看到这一点
TestEventListener listener = appContext.getBean(TestEventListener.class);
或隐式使用@Autowired
将其注入另一个bean。请注意,实际的目标对象是添加的,而不是代理。