Merge branch '6.0.x'
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -41,19 +41,6 @@ public class PayloadApplicationEvent<T> extends ApplicationEvent implements Reso
|
||||
|
||||
private final ResolvableType payloadType;
|
||||
|
||||
/**
|
||||
* Create a new PayloadApplicationEvent.
|
||||
* @param source the object on which the event initially occurred (never {@code null})
|
||||
* @param payload the payload object (never {@code null})
|
||||
* @param payloadType the type object of payload object (can be {@code null})
|
||||
* @since 6.0
|
||||
*/
|
||||
public PayloadApplicationEvent(Object source, T payload, @Nullable ResolvableType payloadType) {
|
||||
super(source);
|
||||
Assert.notNull(payload, "Payload must not be null");
|
||||
this.payload = payload;
|
||||
this.payloadType = (payloadType != null) ? payloadType : ResolvableType.forInstance(payload);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PayloadApplicationEvent, using the instance to infer its type.
|
||||
@@ -64,6 +51,22 @@ public class PayloadApplicationEvent<T> extends ApplicationEvent implements Reso
|
||||
this(source, payload, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new PayloadApplicationEvent based on the provided payload type.
|
||||
* @param source the object on which the event initially occurred (never {@code null})
|
||||
* @param payload the payload object (never {@code null})
|
||||
* @param payloadType the type object of payload object (can be {@code null}).
|
||||
* Note that this is meant to indicate the payload type (e.g. {@code String}),
|
||||
* not the full event type (such as {@code PayloadApplicationEvent<<String>}).
|
||||
* @since 6.0
|
||||
*/
|
||||
public PayloadApplicationEvent(Object source, T payload, @Nullable ResolvableType payloadType) {
|
||||
super(source);
|
||||
Assert.notNull(payload, "Payload must not be null");
|
||||
this.payload = payload;
|
||||
this.payloadType = (payloadType != null ? payloadType : ResolvableType.forInstance(payload));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ResolvableType getResolvableType() {
|
||||
|
||||
@@ -128,12 +128,12 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
||||
|
||||
@Override
|
||||
public void multicastEvent(ApplicationEvent event) {
|
||||
multicastEvent(event, resolveDefaultEventType(event));
|
||||
multicastEvent(event, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
|
||||
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
|
||||
ResolvableType type = (eventType != null ? eventType : ResolvableType.forInstance(event));
|
||||
Executor executor = getTaskExecutor();
|
||||
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
|
||||
if (executor != null) {
|
||||
@@ -145,10 +145,6 @@ public class SimpleApplicationEventMulticaster extends AbstractApplicationEventM
|
||||
}
|
||||
}
|
||||
|
||||
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
|
||||
return ResolvableType.forInstance(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke the given listener with the given event.
|
||||
* @param listener the ApplicationListener to invoke
|
||||
|
||||
@@ -385,23 +385,47 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
|
||||
/**
|
||||
* Publish the given event to all listeners.
|
||||
* <p>This is the internal delegate that all other {@code publishEvent}
|
||||
* methods refer to. It is not meant to be called directly but rather serves
|
||||
* as a propagation mechanism between application contexts in a hierarchy,
|
||||
* potentially overridden in subclasses for a custom propagation arrangement.
|
||||
* @param event the event to publish (may be an {@link ApplicationEvent}
|
||||
* or a payload object to be turned into a {@link PayloadApplicationEvent})
|
||||
* @param eventType the resolved event type, if known
|
||||
* @param typeHint the resolved event type, if known.
|
||||
* The implementation of this method also tolerates a payload type hint for
|
||||
* a payload object to be turned into a {@link PayloadApplicationEvent}.
|
||||
* However, the recommended way is to construct an actual event object via
|
||||
* {@link PayloadApplicationEvent#PayloadApplicationEvent(Object, Object, ResolvableType)}
|
||||
* instead for such scenarios.
|
||||
* @since 4.2
|
||||
* @see ApplicationEventMulticaster#multicastEvent(ApplicationEvent, ResolvableType)
|
||||
*/
|
||||
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
|
||||
protected void publishEvent(Object event, @Nullable ResolvableType typeHint) {
|
||||
Assert.notNull(event, "Event must not be null");
|
||||
ResolvableType eventType = null;
|
||||
|
||||
// Decorate event as an ApplicationEvent if necessary
|
||||
ApplicationEvent applicationEvent;
|
||||
if (event instanceof ApplicationEvent applEvent) {
|
||||
applicationEvent = applEvent;
|
||||
eventType = typeHint;
|
||||
}
|
||||
else {
|
||||
applicationEvent = new PayloadApplicationEvent<>(this, event, eventType);
|
||||
if (eventType == null) {
|
||||
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
|
||||
ResolvableType payloadType = null;
|
||||
if (typeHint != null && ApplicationEvent.class.isAssignableFrom(typeHint.toClass())) {
|
||||
eventType = typeHint;
|
||||
}
|
||||
else {
|
||||
payloadType = typeHint;
|
||||
}
|
||||
applicationEvent = new PayloadApplicationEvent<>(this, event, payloadType);
|
||||
}
|
||||
|
||||
// Determine event type only once (for multicast and parent publish)
|
||||
if (eventType == null) {
|
||||
eventType = ResolvableType.forInstance(applicationEvent);
|
||||
if (typeHint == null) {
|
||||
typeHint = eventType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,7 +440,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
|
||||
// Publish event via parent context as well...
|
||||
if (this.parent != null) {
|
||||
if (this.parent instanceof AbstractApplicationContext abstractApplicationContext) {
|
||||
abstractApplicationContext.publishEvent(event, eventType);
|
||||
abstractApplicationContext.publishEvent(event, typeHint);
|
||||
}
|
||||
else {
|
||||
this.parent.publishEvent(event);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.PayloadApplicationEvent;
|
||||
@@ -68,16 +69,97 @@ class PayloadApplicationEventTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testEventClassWithPayloadType() {
|
||||
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(NumberHolderListener.class);
|
||||
|
||||
PayloadApplicationEvent<NumberHolder<Integer>> event = new PayloadApplicationEvent<>(this,
|
||||
new NumberHolder<>(42), ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
|
||||
ac.publishEvent(event);
|
||||
assertThat(ac.getBean(NumberHolderListener.class).events.contains(event.getPayload())).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testEventClassWithPayloadTypeOnParentContext() {
|
||||
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(NumberHolderListener.class);
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
|
||||
ac.refresh();
|
||||
|
||||
PayloadApplicationEvent<NumberHolder<Integer>> event = new PayloadApplicationEvent<>(this,
|
||||
new NumberHolder<>(42), ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
|
||||
ac.publishEvent(event);
|
||||
assertThat(parent.getBean(NumberHolderListener.class).events.contains(event.getPayload())).isTrue();
|
||||
ac.close();
|
||||
parent.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testPayloadObjectWithPayloadType() {
|
||||
final Object payload = new NumberHolder<>(42);
|
||||
|
||||
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(NumberHolderListener.class) {
|
||||
@Override
|
||||
protected void finishRefresh() throws BeansException {
|
||||
super.finishRefresh();
|
||||
// This is not recommended: use publishEvent(new PayloadApplicationEvent(...)) instead
|
||||
publishEvent(payload, ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
|
||||
}
|
||||
};
|
||||
|
||||
assertThat(ac.getBean(NumberHolderListener.class).events.contains(payload)).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testPayloadObjectWithPayloadTypeOnParentContext() {
|
||||
final Object payload = new NumberHolder<>(42);
|
||||
|
||||
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(NumberHolderListener.class);
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext(parent) {
|
||||
@Override
|
||||
protected void finishRefresh() throws BeansException {
|
||||
super.finishRefresh();
|
||||
// This is not recommended: use publishEvent(new PayloadApplicationEvent(...)) instead
|
||||
publishEvent(payload, ResolvableType.forClassWithGenerics(NumberHolder.class, Integer.class));
|
||||
}
|
||||
};
|
||||
ac.refresh();
|
||||
|
||||
assertThat(parent.getBean(NumberHolderListener.class).events.contains(payload)).isTrue();
|
||||
ac.close();
|
||||
parent.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testEventClassWithInterface() {
|
||||
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(AuditableListener.class);
|
||||
|
||||
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
|
||||
ac.publishEvent(event);
|
||||
assertThat(ac.getBean(AuditableListener.class).events.contains(event)).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testEventClassWithInterfaceOnParentContext() {
|
||||
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(AuditableListener.class);
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
|
||||
ac.refresh();
|
||||
|
||||
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
|
||||
ac.publishEvent(event);
|
||||
assertThat(parent.getBean(AuditableListener.class).events.contains(event)).isTrue();
|
||||
ac.close();
|
||||
parent.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testProgrammaticEventListener() {
|
||||
@@ -96,6 +178,27 @@ class PayloadApplicationEventTests {
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testProgrammaticEventListenerOnParentContext() {
|
||||
List<Auditable> events = new ArrayList<>();
|
||||
ApplicationListener<AuditablePayloadEvent<String>> listener = events::add;
|
||||
ApplicationListener<AuditablePayloadEvent<Integer>> mismatch = (event -> event.getPayload());
|
||||
|
||||
ConfigurableApplicationContext parent = new GenericApplicationContext();
|
||||
parent.addApplicationListener(listener);
|
||||
parent.addApplicationListener(mismatch);
|
||||
parent.refresh();
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
|
||||
ac.refresh();
|
||||
|
||||
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
|
||||
ac.publishEvent(event);
|
||||
assertThat(events.contains(event)).isTrue();
|
||||
ac.close();
|
||||
parent.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testProgrammaticPayloadListener() {
|
||||
@@ -108,12 +211,77 @@ class PayloadApplicationEventTests {
|
||||
ac.addApplicationListener(mismatch);
|
||||
ac.refresh();
|
||||
|
||||
AuditablePayloadEvent<String> event = new AuditablePayloadEvent<>(this, "xyz");
|
||||
ac.publishEvent(event);
|
||||
assertThat(events.contains(event.getPayload())).isTrue();
|
||||
String payload = "xyz";
|
||||
ac.publishEvent(payload);
|
||||
assertThat(events.contains(payload)).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testProgrammaticPayloadListenerOnParentContext() {
|
||||
List<String> events = new ArrayList<>();
|
||||
ApplicationListener<PayloadApplicationEvent<String>> listener = ApplicationListener.forPayload(events::add);
|
||||
ApplicationListener<PayloadApplicationEvent<Integer>> mismatch = ApplicationListener.forPayload(Integer::intValue);
|
||||
|
||||
ConfigurableApplicationContext parent = new GenericApplicationContext();
|
||||
parent.addApplicationListener(listener);
|
||||
parent.addApplicationListener(mismatch);
|
||||
parent.refresh();
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
|
||||
ac.refresh();
|
||||
|
||||
String payload = "xyz";
|
||||
ac.publishEvent(payload);
|
||||
assertThat(events.contains(payload)).isTrue();
|
||||
ac.close();
|
||||
parent.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testPlainPayloadListener() {
|
||||
ConfigurableApplicationContext ac = new AnnotationConfigApplicationContext(PlainPayloadListener.class);
|
||||
|
||||
String payload = "xyz";
|
||||
ac.publishEvent(payload);
|
||||
assertThat(ac.getBean(PlainPayloadListener.class).events.contains(payload)).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("resource")
|
||||
void testPlainPayloadListenerOnParentContext() {
|
||||
ConfigurableApplicationContext parent = new AnnotationConfigApplicationContext(PlainPayloadListener.class);
|
||||
ConfigurableApplicationContext ac = new GenericApplicationContext(parent);
|
||||
ac.refresh();
|
||||
|
||||
String payload = "xyz";
|
||||
ac.publishEvent(payload);
|
||||
assertThat(parent.getBean(PlainPayloadListener.class).events.contains(payload)).isTrue();
|
||||
ac.close();
|
||||
parent.close();
|
||||
}
|
||||
|
||||
|
||||
static class NumberHolder<T extends Number> {
|
||||
|
||||
public NumberHolder(T number) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Component
|
||||
public static class NumberHolderListener {
|
||||
|
||||
public final List<NumberHolder<Integer>> events = new ArrayList<>();
|
||||
|
||||
@EventListener
|
||||
public void onEvent(NumberHolder<Integer> event) {
|
||||
events.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface Auditable {
|
||||
}
|
||||
@@ -139,11 +307,16 @@ class PayloadApplicationEventTests {
|
||||
}
|
||||
}
|
||||
|
||||
static class NumberHolder<T extends Number> {
|
||||
|
||||
public NumberHolder(T number) {
|
||||
@Component
|
||||
public static class PlainPayloadListener {
|
||||
|
||||
public final List<String> events = new ArrayList<>();
|
||||
|
||||
@EventListener
|
||||
public void onEvent(String event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -57,8 +57,9 @@ public abstract class AbstractApplicationContextTests extends AbstractListableBe
|
||||
|
||||
protected TestApplicationListener parentListener = new TestApplicationListener();
|
||||
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() throws Exception {
|
||||
public void setup() throws Exception {
|
||||
this.applicationContext = createContext();
|
||||
}
|
||||
|
||||
@@ -79,6 +80,7 @@ public abstract class AbstractApplicationContextTests extends AbstractListableBe
|
||||
*/
|
||||
protected abstract ConfigurableApplicationContext createContext() throws Exception;
|
||||
|
||||
|
||||
@Test
|
||||
public void contextAwareSingletonWasCalledBack() throws Exception {
|
||||
ACATester aca = (ACATester) applicationContext.getBean("aca");
|
||||
|
||||
Reference in New Issue
Block a user