Browse Source

Expose id/getListenerId in base EventListener/ApplicationListener (pulled up from tx)

Includes removeApplicationListeners(Predicate) method in ApplicationEventMulticaster.

Closes gh-26638
pull/26651/head
Juergen Hoeller 4 years ago
parent
commit
86902d27b2
  1. 14
      spring-context/src/main/java/org/springframework/context/ApplicationListener.java
  2. 19
      spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java
  3. 41
      spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java
  4. 34
      spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java
  5. 12
      spring-context/src/main/java/org/springframework/context/event/EventListener.java
  6. 22
      spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java
  7. 21
      spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListener.java
  8. 33
      spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java
  9. 20
      spring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListener.java
  10. 5
      spring-tx/src/test/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapterTests.java

14
spring-context/src/main/java/org/springframework/context/ApplicationListener.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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.

19
spring-context/src/main/java/org/springframework/context/event/AbstractApplicationEventMulticaster.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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) {

41
spring-context/src/main/java/org/springframework/context/event/ApplicationEventMulticaster.java

@ -1,5 +1,5 @@ @@ -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 @@ @@ -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 { @@ -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();

34
spring-context/src/main/java/org/springframework/context/event/ApplicationListenerMethodAdapter.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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

12
spring-context/src/main/java/org/springframework/context/event/EventListener.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 { @@ -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 "";
}

22
spring-context/src/test/java/org/springframework/context/event/AnnotationDrivenEventListenerTests.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -711,7 +729,7 @@ public class AnnotationDrivenEventListenerTests {
}
@EventListener
@EventListener(id = "foo")
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface FooListener {

21
spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListener.java

@ -1,5 +1,5 @@ @@ -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,25 +59,28 @@ public interface TransactionalApplicationListener<E extends ApplicationEvent> @@ -59,25 +59,28 @@ public interface TransactionalApplicationListener<E extends ApplicationEvent>
return Ordered.LOWEST_PRECEDENCE;
}
/**
* Return the {@link TransactionPhase} in which the listener will be invoked.
* <p>The default phase is {@link TransactionPhase#AFTER_COMMIT}.
*/
default TransactionPhase getTransactionPhase() {
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 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}.
*/
default TransactionPhase getTransactionPhase() {
return TransactionPhase.AFTER_COMMIT;
}
/**
* Add a callback to be invoked on processing within transaction synchronization,
* i.e. when {@link #processEvent} is being triggered during actual transactions.

33
spring-tx/src/main/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapter.java

@ -1,5 +1,5 @@ @@ -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; @@ -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 @@ -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 @@ -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");

20
spring-tx/src/main/java/org/springframework/transaction/event/TransactionalEventListener.java

@ -1,5 +1,5 @@ @@ -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 { @@ -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 { @@ -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 "";
}

5
spring-tx/src/test/java/org/springframework/transaction/event/TransactionalApplicationListenerMethodAdapterTests.java

@ -26,6 +26,7 @@ import org.springframework.core.ResolvableType; @@ -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 { @@ -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 { @@ -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

Loading…
Cancel
Save