diff --git a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
index bb5c0526906..a7f6676a000 100644
--- a/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
+++ b/spring-context/src/test/java/org/springframework/context/event/ApplicationContextEventTests.java
@@ -40,6 +40,11 @@ import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.PayloadApplicationEvent;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.event.test.self_inject.MyApplication;
+import org.springframework.context.event.test.self_inject.MyEventListener;
+import org.springframework.context.event.test.self_inject.MyEventPublisher;
+import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.context.support.StaticMessageSource;
@@ -271,6 +276,24 @@ public class ApplicationContextEventTests extends AbstractApplicationEventListen
assertThat(listener1.seenEvents).hasSize(2);
}
+ /**
+ * Regression test for issue 28283,
+ * where event listeners proxied due to e.g.
+ *
+ * - {@code @Transactional} annotations in their methods or
+ * - being targeted by aspects
+ *
+ * were added to the list of application listener beans twice (both proxy and unwrapped target).
+ */
+ @Test
+ public void eventForSelfInjectedProxiedListenerFiredOnlyOnce() {
+ String basePackage = MyApplication.class.getPackageName();
+ AbstractApplicationContext context = new AnnotationConfigApplicationContext(basePackage);
+ context.getBean(MyEventPublisher.class).publishMyEvent("hello");
+ assertThat(MyEventListener.eventCount).isEqualTo(1);
+ context.close();
+ }
+
@Test
public void testEventPublicationInterceptor() throws Throwable {
MethodInvocation invocation = mock();
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyApplication.java b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyApplication.java
new file mode 100644
index 00000000000..6686c1b121b
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyApplication.java
@@ -0,0 +1,17 @@
+package org.springframework.context.event.test.self_inject;
+
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.context.support.AbstractApplicationContext;
+
+@Configuration
+@EnableAspectJAutoProxy(proxyTargetClass = true)
+public class MyApplication {
+ public static void main(String[] args) {
+ try (AbstractApplicationContext context = new AnnotationConfigApplicationContext("org.springframework.context.event.test.self_inject")) {
+ context.getBean(MyEventPublisher.class).publishMyEvent("hello");
+ assert MyEventListener.eventCount == 1 : "event listener must fire exactly once";
+ }
+ }
+}
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyAspect.java b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyAspect.java
new file mode 100644
index 00000000000..9b037195a9e
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyAspect.java
@@ -0,0 +1,15 @@
+package org.springframework.context.event.test.self_inject;
+
+import org.aspectj.lang.JoinPoint;
+import org.aspectj.lang.annotation.Aspect;
+import org.aspectj.lang.annotation.Before;
+import org.springframework.stereotype.Component;
+
+@Aspect
+@Component
+public class MyAspect {
+ @Before("within(org.springframework.context.event.test.self_inject.MyEventListener)")
+ public void myAdvice(JoinPoint joinPoint) {
+ //System.out.println(joinPoint);
+ }
+}
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEvent.java b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEvent.java
new file mode 100644
index 00000000000..2dc0f2bccf7
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEvent.java
@@ -0,0 +1,12 @@
+package org.springframework.context.event.test.self_inject;
+
+import org.springframework.context.ApplicationEvent;
+
+public class MyEvent extends ApplicationEvent {
+ private String message;
+
+ public MyEvent(Object source, String message) {
+ super(source);
+ this.message = message;
+ }
+}
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEventListener.java b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEventListener.java
new file mode 100644
index 00000000000..5fddae77c05
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEventListener.java
@@ -0,0 +1,20 @@
+package org.springframework.context.event.test.self_inject;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationListener;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MyEventListener implements ApplicationListener {
+ public static int eventCount;
+
+ @Autowired // use '-Dspring.main.allow-circular-references=true' in Spring Boot >= 2.6.0
+ //@Lazy // with '@Lazy', the problem does not occur
+ private MyEventListener eventDemoListener;
+
+ @Override
+ public void onApplicationEvent(MyEvent event) {
+ //System.out.println("Event: " + event);
+ eventCount++;
+ }
+}
diff --git a/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEventPublisher.java b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEventPublisher.java
new file mode 100644
index 00000000000..0959134f7c8
--- /dev/null
+++ b/spring-context/src/test/java/org/springframework/context/event/test/self_inject/MyEventPublisher.java
@@ -0,0 +1,15 @@
+package org.springframework.context.event.test.self_inject;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.stereotype.Component;
+
+@Component
+public class MyEventPublisher {
+ @Autowired
+ private ApplicationEventPublisher eventPublisher;
+
+ public void publishMyEvent(String message) {
+ eventPublisher.publishEvent(new MyEvent(this, message));
+ }
+}