使用 AOP 和 DI 本身会导致 Spring ApplicationListener 被触发两次
Using AOP and DI itself causes Spring ApplicationListener to be fired twice
软件版本
- Spring 版本 5.3.18 及更早版本
- JDK 版本 1.8.0_202
概述
当我使用SpringApplicationListener时,为了防止事务失效,我的ApplicationListener实现class写了下面的代码(当然,代码可以写成不同的方式来避免这个问题),这将导致我的监听器在事件发布后触发两次。我觉得不正常,但不确定是不是bug,所以想问问大家的意见
@Component
public class EventDemoListener implements ApplicationListener<EventDemo> {
@Autowired
DemoService1 demoService1;
@Autowired
DemoService2 demoService2;
@Autowired
EventDemoListener eventDemoListener;
@Override
public void onApplicationEvent(EventDemo event) {
eventDemoListener.testTransaction();
System.out.println("receiver " + event.getMessage());
}
@Transactional(rollbackFor = Exception.class)
public void testTransaction() {
demoService1.doService();
demoService2.doService();
}
}
通过这个demo工程,可以复现这个问题。请在运行前阅读README.md文档。
https://github.com/ZiFeng-Wu/spring-study
分析
经过分析,因为这里DI本身,在创建EventDemoListener
时,属性填充会提前触发DefaultSingletonBeanRegistry#getSingleton(String, boolean)
然后在getSingleton()
中执行的singletonFactory.getObject()
会导致未代理的EventDemoListener
对象被放入AbstractAutoProxyCreator#earlyProxyReferences
.
属性填充后,调用AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)
并执行ApplicationListenerDetector#postProcessAfterInitialization(Object, String)
会导致未代理的EventDemoListener
对象放入AbstractApplicationEventMulticaster.DefaultListenerRetriever#applicationListeners
容器.
然后事件发布的时候,执行AbstractApplicationEventMulticaster.DefaultListenerRetriever#getApplicationListeners()
,使用ApplicationListener<?> listener =beanFactory.getBean(listenerBeanName, ApplicationListener.class)
获取监听器就是被代理的EventDemoListener
对象
此时applicationListeners
容器中只有未代理的EventDemoListener
对象,所以代理后的EventDemoListener
对象会添加到最终返回的allListeners中集合,如下图所示,最终会导致监听器被触发两次。
更新答案
现在使用您更新的 GitHub 项目,我可以重现该问题。当使用 Spring AOP 方面以侦听器 class 为目标时,也会发生这种情况,而不仅仅是 self-injection + @Transactional
的特殊情况。 IMO,这是一个 Spring 核心错误,这就是为什么我在这里创建 PR #28322 in order to fix the issue #28283 你在 cross-posting 之前或之后提出的问题。你应该在你的问题中链接到那个问题,我刚刚找到它是因为我在为它自己创建问题之前搜索关键字。
另请参阅我在问题中的评论,从 this one 开始。
原回答(供参考)
好的,在你的主class我改了
String configFile = "src/main/resources/spring-context.xml";
AbstractApplicationContext context = new FileSystemXmlApplicationContext(configFile);
至
AbstractApplicationContext context = new AnnotationConfigApplicationContext("com.zifeng.spring");
现在应用程序启动了,同样没有数据库配置。它只是打印:
receiver test
也不例外。也就是说,如果它对您不起作用,可能是您的 XML 配置中存在错误。但实际上,你真的不需要它,因为你已经使用了组件和服务注解。
所以如果我需要一个数据库设置来重现这个,请像我在评论中说的那样更新项目以提供开箱即用的 H2 配置。
软件版本
- Spring 版本 5.3.18 及更早版本
- JDK 版本 1.8.0_202
概述
当我使用SpringApplicationListener时,为了防止事务失效,我的ApplicationListener实现class写了下面的代码(当然,代码可以写成不同的方式来避免这个问题),这将导致我的监听器在事件发布后触发两次。我觉得不正常,但不确定是不是bug,所以想问问大家的意见
@Component
public class EventDemoListener implements ApplicationListener<EventDemo> {
@Autowired
DemoService1 demoService1;
@Autowired
DemoService2 demoService2;
@Autowired
EventDemoListener eventDemoListener;
@Override
public void onApplicationEvent(EventDemo event) {
eventDemoListener.testTransaction();
System.out.println("receiver " + event.getMessage());
}
@Transactional(rollbackFor = Exception.class)
public void testTransaction() {
demoService1.doService();
demoService2.doService();
}
}
通过这个demo工程,可以复现这个问题。请在运行前阅读README.md文档。
https://github.com/ZiFeng-Wu/spring-study
分析
经过分析,因为这里DI本身,在创建
EventDemoListener
时,属性填充会提前触发DefaultSingletonBeanRegistry#getSingleton(String, boolean)
然后在
getSingleton()
中执行的singletonFactory.getObject()
会导致未代理的EventDemoListener
对象被放入AbstractAutoProxyCreator#earlyProxyReferences
.属性填充后,调用
AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)
并执行ApplicationListenerDetector#postProcessAfterInitialization(Object, String)
会导致未代理的EventDemoListener
对象放入AbstractApplicationEventMulticaster.DefaultListenerRetriever#applicationListeners
容器.然后事件发布的时候,执行
AbstractApplicationEventMulticaster.DefaultListenerRetriever#getApplicationListeners()
,使用ApplicationListener<?> listener =beanFactory.getBean(listenerBeanName, ApplicationListener.class)
获取监听器就是被代理的EventDemoListener
对象此时
applicationListeners
容器中只有未代理的EventDemoListener
对象,所以代理后的EventDemoListener
对象会添加到最终返回的allListeners中集合,如下图所示,最终会导致监听器被触发两次。
更新答案
现在使用您更新的 GitHub 项目,我可以重现该问题。当使用 Spring AOP 方面以侦听器 class 为目标时,也会发生这种情况,而不仅仅是 self-injection + @Transactional
的特殊情况。 IMO,这是一个 Spring 核心错误,这就是为什么我在这里创建 PR #28322 in order to fix the issue #28283 你在 cross-posting 之前或之后提出的问题。你应该在你的问题中链接到那个问题,我刚刚找到它是因为我在为它自己创建问题之前搜索关键字。
另请参阅我在问题中的评论,从 this one 开始。
原回答(供参考)
好的,在你的主class我改了
String configFile = "src/main/resources/spring-context.xml";
AbstractApplicationContext context = new FileSystemXmlApplicationContext(configFile);
至
AbstractApplicationContext context = new AnnotationConfigApplicationContext("com.zifeng.spring");
现在应用程序启动了,同样没有数据库配置。它只是打印:
receiver test
也不例外。也就是说,如果它对您不起作用,可能是您的 XML 配置中存在错误。但实际上,你真的不需要它,因为你已经使用了组件和服务注解。
所以如果我需要一个数据库设置来重现这个,请像我在评论中说的那样更新项目以提供开箱即用的 H2 配置。