Expose id/getListenerId in base EventListener/ApplicationListener (pulled up from tx)
Includes removeApplicationListeners(Predicate) method in ApplicationEventMulticaster. Closes gh-26638
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.context;
|
||||
|
||||
import java.util.EventListener;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Interface to be implemented by application event listeners.
|
||||
@@ -46,6 +47,17 @@ public interface ApplicationListener<E extends ApplicationEvent> extends EventLi
|
||||
*/
|
||||
void onApplicationEvent(E event);
|
||||
|
||||
/**
|
||||
* Return an optional identifier for the listener.
|
||||
* <p>The default value is an empty String.
|
||||
* @since 5.3.5
|
||||
* @see org.springframework.context.event.EventListener#id
|
||||
* @see org.springframework.context.event.ApplicationEventMulticaster#removeApplicationListeners(Predicate)
|
||||
*/
|
||||
default String getListenerId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code ApplicationListener} for the given payload consumer.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -23,6 +23,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.aop.framework.AopProxyUtils;
|
||||
import org.springframework.beans.factory.BeanClassLoaderAware;
|
||||
@@ -137,6 +138,22 @@ public abstract class AbstractApplicationEventMulticaster
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate) {
|
||||
synchronized (this.defaultRetriever) {
|
||||
this.defaultRetriever.applicationListeners.removeIf(predicate);
|
||||
this.retrieverCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeApplicationListenerBeans(Predicate<String> predicate) {
|
||||
synchronized (this.defaultRetriever) {
|
||||
this.defaultRetriever.applicationListenerBeans.removeIf(predicate);
|
||||
this.retrieverCache.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAllListeners() {
|
||||
synchronized (this.defaultRetriever) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.context.event;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.core.ResolvableType;
|
||||
@@ -39,31 +41,68 @@ public interface ApplicationEventMulticaster {
|
||||
/**
|
||||
* Add a listener to be notified of all events.
|
||||
* @param listener the listener to add
|
||||
* @see #removeApplicationListener(ApplicationListener)
|
||||
* @see #removeApplicationListeners(Predicate)
|
||||
*/
|
||||
void addApplicationListener(ApplicationListener<?> listener);
|
||||
|
||||
/**
|
||||
* Add a listener bean to be notified of all events.
|
||||
* @param listenerBeanName the name of the listener bean to add
|
||||
* @see #removeApplicationListenerBean(String)
|
||||
* @see #removeApplicationListenerBeans(Predicate)
|
||||
*/
|
||||
void addApplicationListenerBean(String listenerBeanName);
|
||||
|
||||
/**
|
||||
* Remove a listener from the notification list.
|
||||
* @param listener the listener to remove
|
||||
* @see #addApplicationListener(ApplicationListener)
|
||||
* @see #removeApplicationListeners(Predicate)
|
||||
*/
|
||||
void removeApplicationListener(ApplicationListener<?> listener);
|
||||
|
||||
/**
|
||||
* Remove a listener bean from the notification list.
|
||||
* @param listenerBeanName the name of the listener bean to remove
|
||||
* @see #addApplicationListenerBean(String)
|
||||
* @see #removeApplicationListenerBeans(Predicate)
|
||||
*/
|
||||
void removeApplicationListenerBean(String listenerBeanName);
|
||||
|
||||
/**
|
||||
* Remove all matching listeners from the set of registered
|
||||
* {@code ApplicationListener} instances (which includes adapter classes
|
||||
* such as {@link ApplicationListenerMethodAdapter}, e.g. for annotated
|
||||
* {@link EventListener} methods).
|
||||
* <p>Note: This just applies to instance registrations, not to listeners
|
||||
* registered by bean name.
|
||||
* @param predicate the predicate to identify listener instances to remove,
|
||||
* e.g. checking {@link ApplicationListener#getListenerId()}
|
||||
* @since 5.3.5
|
||||
* @see #addApplicationListener(ApplicationListener)
|
||||
* @see #removeApplicationListener(ApplicationListener)
|
||||
*/
|
||||
void removeApplicationListeners(Predicate<ApplicationListener<?>> predicate);
|
||||
|
||||
/**
|
||||
* Remove all matching listener beans from the set of registered
|
||||
* listener bean names (referring to bean classes which in turn
|
||||
* implement the {@link ApplicationListener} interface directly).
|
||||
* <p>Note: This just applies to bean name registrations, not to
|
||||
* programmatically registered {@code ApplicationListener} instances.
|
||||
* @param predicate the predicate to identify listener bean names to remove
|
||||
* @since 5.3.5
|
||||
* @see #addApplicationListenerBean(String)
|
||||
* @see #removeApplicationListenerBean(String)
|
||||
*/
|
||||
void removeApplicationListenerBeans(Predicate<String> predicate);
|
||||
|
||||
/**
|
||||
* Remove all listeners registered with this multicaster.
|
||||
* <p>After a remove call, the multicaster will perform no action
|
||||
* on event notification until new listeners are registered.
|
||||
* @see #removeApplicationListeners(Predicate)
|
||||
*/
|
||||
void removeAllListeners();
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -24,6 +24,7 @@ import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.concurrent.CompletionStage;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -89,6 +90,9 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
|
||||
private final int order;
|
||||
|
||||
@Nullable
|
||||
private volatile String listenerId;
|
||||
|
||||
@Nullable
|
||||
private ApplicationContext applicationContext;
|
||||
|
||||
@@ -113,6 +117,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
this.declaredEventTypes = resolveDeclaredEventTypes(method, ann);
|
||||
this.condition = (ann != null ? ann.condition() : null);
|
||||
this.order = resolveOrder(this.targetMethod);
|
||||
String id = (ann != null ? ann.id() : "");
|
||||
this.listenerId = (!id.isEmpty() ? id : null);
|
||||
}
|
||||
|
||||
private static List<ResolvableType> resolveDeclaredEventTypes(Method method, @Nullable EventListener ann) {
|
||||
@@ -186,6 +192,32 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
|
||||
return this.order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getListenerId() {
|
||||
String id = this.listenerId;
|
||||
if (id == null) {
|
||||
id = getDefaultListenerId();
|
||||
this.listenerId = id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the default id for the target listener, to be applied in case of
|
||||
* no {@link EventListener#id() annotation-specified id value}.
|
||||
* <p>The default implementation builds a method name with parameter types.
|
||||
* @since 5.3.5
|
||||
* @see #getListenerId()
|
||||
*/
|
||||
protected String getDefaultListenerId() {
|
||||
Method method = getTargetMethod();
|
||||
StringJoiner sj = new StringJoiner(",", "(", ")");
|
||||
for (Class<?> paramType : method.getParameterTypes()) {
|
||||
sj.add(paramType.getName());
|
||||
}
|
||||
return ClassUtils.getQualifiedMethodName(method) + sj.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Process the specified {@link ApplicationEvent}, checking if the condition
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.core.annotation.AliasFor;
|
||||
@@ -128,4 +129,13 @@ public @interface EventListener {
|
||||
*/
|
||||
String condition() default "";
|
||||
|
||||
/**
|
||||
* An optional identifier for the listener, defaulting to the fully-qualified
|
||||
* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
|
||||
* @since 5.3.5
|
||||
* @see org.springframework.context.ApplicationListener#getListenerId()
|
||||
* @see ApplicationEventMulticaster#removeApplicationListeners(Predicate)
|
||||
*/
|
||||
String id() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -111,6 +111,12 @@ public class AnnotationDrivenEventListenerTests {
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertEvent(listener, event);
|
||||
this.eventCollector.assertTotalEventsCount(1);
|
||||
|
||||
context.getBean(ApplicationEventMulticaster.class).removeApplicationListeners(
|
||||
l -> l.getListenerId().contains("TestEvent"));
|
||||
this.eventCollector.clear();
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertNoEventReceived(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -126,6 +132,12 @@ public class AnnotationDrivenEventListenerTests {
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertEvent(listener, event);
|
||||
this.eventCollector.assertTotalEventsCount(1);
|
||||
|
||||
context.getBean(ApplicationEventMulticaster.class).removeApplicationListeners(
|
||||
l -> l.getListenerId().contains("TestEvent"));
|
||||
this.eventCollector.clear();
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertNoEventReceived(listener);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -138,6 +150,12 @@ public class AnnotationDrivenEventListenerTests {
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertEvent(bean, event);
|
||||
this.eventCollector.assertTotalEventsCount(1);
|
||||
|
||||
context.getBean(ApplicationEventMulticaster.class).removeApplicationListeners(
|
||||
l -> l.getListenerId().equals("foo"));
|
||||
this.eventCollector.clear();
|
||||
this.context.publishEvent(event);
|
||||
this.eventCollector.assertNoEventReceived(bean);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -711,7 +729,7 @@ public class AnnotationDrivenEventListenerTests {
|
||||
}
|
||||
|
||||
|
||||
@EventListener
|
||||
@EventListener(id = "foo")
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface FooListener {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -59,6 +59,20 @@ public interface TransactionalApplicationListener<E extends ApplicationEvent>
|
||||
return Ordered.LOWEST_PRECEDENCE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an identifier for the listener to be able to refer to it individually.
|
||||
* <p>It might be necessary for specific completion callback implementations
|
||||
* to provide a specific id, whereas for other scenarios an empty String
|
||||
* (as the common default value) is acceptable as well.
|
||||
* @see ApplicationListener#getListenerId()
|
||||
* @see TransactionalEventListener#id
|
||||
* @see #addCallback
|
||||
*/
|
||||
@Override
|
||||
default String getListenerId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link TransactionPhase} in which the listener will be invoked.
|
||||
* <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.
|
||||
@@ -67,17 +81,6 @@ public interface TransactionalApplicationListener<E extends ApplicationEvent>
|
||||
return TransactionPhase.AFTER_COMMIT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an identifier for the listener to be able to refer to it individually.
|
||||
* <p>It might be necessary for specific completion callback implementations
|
||||
* to provide a specific id, whereas for other scenarios an empty String
|
||||
* (as the common default value) is acceptable as well.
|
||||
* @see #addCallback
|
||||
*/
|
||||
default String getListenerId() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to be invoked on processing within transaction synchronization,
|
||||
* i.e. when {@link #processEvent} is being triggered during actual transactions.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -25,11 +25,8 @@ import org.springframework.context.event.ApplicationListenerMethodAdapter;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.context.event.GenericApplicationListener;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* {@link GenericApplicationListener} adapter that delegates the processing of
|
||||
@@ -55,9 +52,6 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi
|
||||
|
||||
private final TransactionPhase transactionPhase;
|
||||
|
||||
@Nullable
|
||||
private volatile String listenerId;
|
||||
|
||||
private final List<SynchronizationCallback> callbacks = new CopyOnWriteArrayList<>();
|
||||
|
||||
|
||||
@@ -84,31 +78,6 @@ public class TransactionalApplicationListenerMethodAdapter extends ApplicationLi
|
||||
return this.transactionPhase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getListenerId() {
|
||||
String id = this.listenerId;
|
||||
if (id == null) {
|
||||
id = this.annotation.id();
|
||||
if (id.isEmpty()) {
|
||||
id = getDefaultListenerId();
|
||||
}
|
||||
this.listenerId = id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the default id for the target listener, to be applied in case of
|
||||
* no {@link TransactionalEventListener#id() annotation-specified id value}.
|
||||
* <p>The default implementation builds a method name with parameter types.
|
||||
* @see #getListenerId()
|
||||
*/
|
||||
protected String getDefaultListenerId() {
|
||||
Method method = getTargetMethod();
|
||||
return ClassUtils.getQualifiedMethodName(method) +
|
||||
"(" + StringUtils.arrayToDelimitedString(method.getParameterTypes(), ",") + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCallback(SynchronizationCallback callback) {
|
||||
Assert.notNull(callback, "SynchronizationCallback must not be null");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 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.
|
||||
@@ -64,13 +64,6 @@ public @interface TransactionalEventListener {
|
||||
*/
|
||||
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
|
||||
|
||||
/**
|
||||
* An optional identifier to uniquely reference the listener.
|
||||
* @since 5.3
|
||||
* @see TransactionalApplicationListener#getListenerId()
|
||||
*/
|
||||
String id() default "";
|
||||
|
||||
/**
|
||||
* Whether the event should be processed if no transaction is running.
|
||||
*/
|
||||
@@ -98,6 +91,17 @@ public @interface TransactionalEventListener {
|
||||
* <p>The default is {@code ""}, meaning the event is always handled.
|
||||
* @see EventListener#condition
|
||||
*/
|
||||
@AliasFor(annotation = EventListener.class, attribute = "condition")
|
||||
String condition() default "";
|
||||
|
||||
/**
|
||||
* An optional identifier for the listener, defaulting to the fully-qualified
|
||||
* signature of the declaring method (e.g. "mypackage.MyClass.myMethod()").
|
||||
* @since 5.3
|
||||
* @see EventListener#id
|
||||
* @see TransactionalApplicationListener#getListenerId()
|
||||
*/
|
||||
@AliasFor(annotation = EventListener.class, attribute = "id")
|
||||
String id() default "";
|
||||
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
@@ -81,7 +82,7 @@ public class TransactionalApplicationListenerMethodAdapterTests {
|
||||
assertThat(callback.postEvent).isEqualTo(event);
|
||||
assertThat(callback.ex).isNull();
|
||||
assertThat(adapter.getTransactionPhase()).isEqualTo(TransactionPhase.AFTER_COMMIT);
|
||||
assertThat(adapter.getListenerId()).endsWith("SampleEvents.defaultPhase(class java.lang.String)");
|
||||
assertThat(adapter.getListenerId()).endsWith("SampleEvents.defaultPhase(java.lang.String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -102,7 +103,7 @@ public class TransactionalApplicationListenerMethodAdapterTests {
|
||||
assertThat(callback.ex).isInstanceOf(RuntimeException.class);
|
||||
assertThat(callback.ex.getMessage()).isEqualTo("event");
|
||||
assertThat(adapter.getTransactionPhase()).isEqualTo(TransactionPhase.BEFORE_COMMIT);
|
||||
assertThat(adapter.getListenerId()).isEqualTo(adapter.getDefaultListenerId());
|
||||
assertThat(adapter.getListenerId()).isEqualTo(ClassUtils.getQualifiedMethodName(m) + "(java.lang.String)");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Reference in New Issue
Block a user