mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 16:39:29 +00:00
Use dedicated ApplicationEventFactory interface in EventPublicationInterceptor
See gh-36072
This commit is contained in:
+56
-16
@@ -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 <i>successful</i> method invocation.
|
||||
*
|
||||
* <p>Note that this interceptor is capable of publishing a custom event after each
|
||||
* <i>successful</i> 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.
|
||||
*
|
||||
* <p>As of 7.0.3, this interceptor publishes a {@link MethodFailureEvent} for
|
||||
* every exception encountered from a method invocation. This can be conveniently
|
||||
* <p>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<MethodFailureEvent>} class or an
|
||||
* {@code @EventListener(MethodFailureEvent.class)} method.
|
||||
* {@code @EventListener(MethodFailureEvent.class)} method. The failure event can be
|
||||
* customized through overriding the {@link ApplicationEventFactory#onFailure} method.
|
||||
*
|
||||
* @author Dmitriy Kopylenko
|
||||
* @author Juergen Hoeller
|
||||
@@ -57,13 +58,13 @@ import org.springframework.util.Assert;
|
||||
public class EventPublicationInterceptor
|
||||
implements MethodInterceptor, ApplicationEventPublisherAware, InitializingBean {
|
||||
|
||||
private @Nullable Function<MethodInvocation, ? extends ApplicationEvent> applicationEventFactory;
|
||||
private ApplicationEventFactory applicationEventFactory = (invocation, returnValue) -> null;
|
||||
|
||||
private @Nullable ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
|
||||
/**
|
||||
* Set the application event class to publish.
|
||||
* Set the application event class to publish after each successful invocation.
|
||||
* <p>The event class <b>must</b> 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 <i>successful</i> method invocation.
|
||||
* {@link MethodInvocation}, representing each <i>successful</i> method invocation.
|
||||
* @since 7.0.3
|
||||
* @see #setApplicationEventClass
|
||||
*/
|
||||
public void setApplicationEventFactory(Function<MethodInvocation, ? extends ApplicationEvent> factoryFunction) {
|
||||
this.applicationEventFactory = factoryFunction;
|
||||
public void setApplicationEventFactory(ApplicationEventFactory applicationEventFactory) {
|
||||
this.applicationEventFactory = applicationEventFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,14 +121,52 @@ public class EventPublicationInterceptor
|
||||
retVal = invocation.proceed();
|
||||
}
|
||||
catch (Throwable ex) {
|
||||
this.applicationEventPublisher.publishEvent(new MethodFailureEvent(invocation, ex));
|
||||
// Publish event after failed invocation.
|
||||
ApplicationEvent event = this.applicationEventFactory.onFailure(invocation, ex);
|
||||
if (event != null) {
|
||||
this.applicationEventPublisher.publishEvent(event);
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
|
||||
if (this.applicationEventFactory != null) {
|
||||
this.applicationEventPublisher.publishEvent(this.applicationEventFactory.apply(invocation));
|
||||
// Publish event after successful invocation.
|
||||
ApplicationEvent event = this.applicationEventFactory.onSuccess(invocation, retVal);
|
||||
if (event != null) {
|
||||
this.applicationEventPublisher.publishEvent(event);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Callback interface for building an {@link ApplicationEvent} after a method invocation.
|
||||
* @since 7.0.3
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ApplicationEventFactory {
|
||||
|
||||
/**
|
||||
* Build an {@link ApplicationEvent} for the given successful method invocation.
|
||||
* <p>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.
|
||||
* <p>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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+26
-1
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user