gpt4 book ai didi

spring - 使用 ScopedProxyMode.TARGET_CLASS 时在监听器上收到重复事件

转载 作者:行者123 更新时间:2023-12-02 01:57:23 30 4
gpt4 key购买 nike

在某些情况下,我们需要在 ApplicationListener 中的 Spring 应用程序中写入数据库,因此我们需要使用 @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 两次包含相同的监听器(相同实例:[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]

@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 被插入到监听器列表中两次。

例子:

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"));
}
}

最佳答案

我不知道这是一个错误还是预期的行为,但这是肮脏的:

像这样声明一个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

ApplicationContext 将使用这些 bean 定义来创建两个 bean:

  1. 一个ScopedProxyFactoryBean bean。这是一个将 TestEventListenerImpl 对象包装在代理中的 FactoryBean
  2. TestEventListenerImpl bean。实际的 TestEventListenerImpl 对象。

初始化过程的一部分是注册实现ApplicationListener 接口(interface)的bean。 TestEventListenerImpl bean 被急切地(立即)创建并注册为 ApplicationListener

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

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

或者通过使用 @Autowired 隐式地将其注入(inject)到另一个 bean 中。请注意,添加的是实际目标对象,而不是代理。

关于spring - 使用 ScopedProxyMode.TARGET_CLASS 时在监听器上收到重复事件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19184386/

30 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com