Software versions
- Spring Version 5.3.18 and earlier
- JDK Version 1.8.0_202
Overview
When I use Spring ApplicationListener, in order to prevent transaction invalidation, my ApplicationListener implementation class writes the following code (of course, the code can be written differently to avoid this problem), which will cause my listener to trigger twice after the event is published. I think it’s not normal, but not sure if it’s a bug, so I want to ask everyone’s opinion.
@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(); } }
Through this demo project, this problem can be reproduced. Please read the README.md document before running.
https://github.com/ZiFeng-Wu/spring-study
Analysis
After analysis, because here DI itself , When
EventDemoListener
is created, property filling will triggerDefaultSingletonBeanRegistry#getSingleton(String, boolean)
in advance.Then
singletonFactory.getObject()
executed ingetSingleton()
will cause the unproxyedEventDemoListener
object to be put intoAbstractAutoProxyCreator#earlyProxyReferences
.After the properties are filled, calling
AbstractAutowireCapableBeanFactory#initializeBean(String, Object, RootBeanDefinition)
and executingApplicationListenerDetector#postProcessAfterInitialization(Object, String)
will cause the unproxyedEventDemoListener
object to be put into theAbstractApplicationEventMulticaster.DefaultListenerRetriever#applicationListeners
container.Then when the event is published, execute
AbstractApplicationEventMulticaster.DefaultListenerRetriever#getApplicationListeners()
and useApplicationListener<?> listener =beanFactory.getBean(listenerBeanName, ApplicationListener.class)
to obtain the listener is the proxiedEventDemoListener
object.At this time, there are only unproxyed
EventDemoListener
object in theapplicationListeners
container, so the proxiedEventDemoListener
object will be added to the final returned allListeners collection, as shown in the figure below, which will eventually cause the listener to be triggered twice.
Advertisement
Answer
Updated answer
Now with your updated GitHub project, I can reproduce the problem. It also occurs when using a Spring AOP aspect targeting the listener class, not just in the special case of self-injection + @Transactional
. IMO, it is a Spring Core bug, which is why I created PR #28322 in order to fix the issue #28283 you raised either before or after cross-posting here. You should have linked to that issue in your question, I just found it because I was searching for key words before creating an issue for it myself.
See also my comment in the issue, starting with this one.
Original answer (for reference)
OK, in your main class I changed
String configFile = "src/main/resources/spring-context.xml"; AbstractApplicationContext context = new FileSystemXmlApplicationContext(configFile);
to
AbstractApplicationContext context = new AnnotationConfigApplicationContext("com.zifeng.spring");
Now the application starts, also without DB configuration. It simply prints:
receiver test
There is no exception. I.e., if for you it does not work, probably there is a bug in your XML configuration. But actually, you really do not need it, because you used component and service annotations already.
So if I need a database setup in order to reproduce this, please, like I said in my comment, update the project to provide an H2 configuration which works out of the box.