diff --git a/spring-context/src/main/java/org/springframework/context/event/EventPublicationInterceptor.java b/spring-context/src/main/java/org/springframework/context/event/EventPublicationInterceptor.java index c24aef27a70..5643eaa30e1 100644 --- a/spring-context/src/main/java/org/springframework/context/event/EventPublicationInterceptor.java +++ b/spring-context/src/main/java/org/springframework/context/event/EventPublicationInterceptor.java @@ -17,7 +17,6 @@ package org.springframework.context.event; import java.lang.reflect.Constructor; -import java.util.function.Function; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -31,19 +30,21 @@ import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.util.Assert; /** - * {@link MethodInterceptor Interceptor} that publishes an {@code ApplicationEvent} - * to all {@code ApplicationListeners} registered with an {@code ApplicationEventPublisher}. + * {@link MethodInterceptor Interceptor} that publishes an {@code ApplicationEvent} to + * all {@code ApplicationListeners} registered with an {@code ApplicationEventPublisher} * after each successful method invocation. * *
Note that this interceptor is capable of publishing a custom event after each * successful method invocation, configured via the * {@link #setApplicationEventClass "applicationEventClass"} property. As of 7.0.3, - * you can configure a {@link #setApplicationEventFactory factory function} instead. + * you can configure a {@link #setApplicationEventFactory factory function} instead, + * implementing the primary {@link ApplicationEventFactory#onSuccess} method there. * - *
As of 7.0.3, this interceptor publishes a {@link MethodFailureEvent} for - * every exception encountered from a method invocation. This can be conveniently + *
By default (as of 7.0.3), this interceptor publishes a {@link MethodFailureEvent}
+ * for every exception encountered from a method invocation. This can be conveniently
* tracked via an {@code ApplicationListener The event class must have a constructor with a single
* {@code Object} argument for the event source. The interceptor
* will pass in the invoked object.
@@ -79,7 +80,8 @@ public class EventPublicationInterceptor
}
try {
Constructor extends ApplicationEvent> ctor = applicationEventClass.getConstructor(Object.class);
- this.applicationEventFactory = (invocation -> BeanUtils.instantiateClass(ctor, invocation.getThis()));
+ this.applicationEventFactory = ((invocation, returnValue) ->
+ BeanUtils.instantiateClass(ctor, invocation.getThis()));
}
catch (NoSuchMethodException ex) {
throw new IllegalArgumentException("ApplicationEvent class [" +
@@ -89,12 +91,12 @@ public class EventPublicationInterceptor
/**
* Specify a factory function for {@link ApplicationEvent} instances built from a
- * {@link MethodInvocation}, representing a successful method invocation.
+ * {@link MethodInvocation}, representing each successful method invocation.
* @since 7.0.3
* @see #setApplicationEventClass
*/
- public void setApplicationEventFactory(Function This is the primary method to implement since there is no such default event.
+ * This may also return {@code null} for not publishing an event on success at all.
+ * @param invocation the successful method invocation
+ * @param returnValue the value that the method returned, if any
+ * @return the event to publish, or {@code null} for none
+ */
+ @Nullable ApplicationEvent onSuccess(MethodInvocation invocation, @Nullable Object returnValue);
+
+ /**
+ * Build an {@link ApplicationEvent} for the given failed method invocation.
+ * The default implementation builds a common {@link MethodFailureEvent}.
+ * This can be overridden to build a custom event instead, or to return
+ * {@code null} for not publishing an event on failure at all.
+ * @param invocation the failed method invocation
+ * @param failure the exception thrown from the method
+ * @return the event to publish, or {@code null} for none
+ */
+ default @Nullable ApplicationEvent onFailure(MethodInvocation invocation, Throwable failure) {
+ return new MethodFailureEvent(invocation, failure);
+ }
+ }
+
}
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 4f04a29ed8b..1f2f22efe80 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
@@ -24,6 +24,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import org.aopalliance.intercept.MethodInvocation;
+import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
@@ -320,7 +321,7 @@ class ApplicationContextEventTests extends AbstractApplicationEventListenerTests
ApplicationContext ctx = mock();
EventPublicationInterceptor interceptor = new EventPublicationInterceptor();
- interceptor.setApplicationEventFactory(inv -> new MyEvent(invocation.getThis()));
+ interceptor.setApplicationEventFactory((inv, retVal) -> new MyEvent(inv.getThis()));
interceptor.setApplicationEventPublisher(ctx);
interceptor.afterPropertiesSet();
@@ -344,6 +345,30 @@ class ApplicationContextEventTests extends AbstractApplicationEventListenerTests
verify(ctx).publishEvent(isA(MethodFailureEvent.class));
}
+ @Test
+ void testEventPublicationInterceptorWithCustomFailure() throws Throwable {
+ MethodInvocation invocation = mock();
+ ApplicationContext ctx = mock();
+
+ EventPublicationInterceptor interceptor = new EventPublicationInterceptor();
+ interceptor.setApplicationEventFactory(new EventPublicationInterceptor.ApplicationEventFactory() {
+ @Override
+ public ApplicationEvent onSuccess(MethodInvocation invocation, @Nullable Object returnValue) {
+ return new MyEvent(returnValue);
+ }
+ @Override
+ public ApplicationEvent onFailure(MethodInvocation invocation, Throwable failure) {
+ return new MyOtherEvent(failure);
+ }
+ });
+ interceptor.setApplicationEventPublisher(ctx);
+ interceptor.afterPropertiesSet();
+
+ given(invocation.proceed()).willThrow(new IllegalStateException());
+ assertThatIllegalStateException().isThrownBy(() -> interceptor.invoke(invocation));
+ verify(ctx).publishEvent(isA(MyOtherEvent.class));
+ }
+
@Test
void listenersInApplicationContext() {
StaticApplicationContext context = new StaticApplicationContext();