Skip to content

Commit f0fca89

Browse files
committed
Annotation-based event listeners
Add support for annotation-based event listeners. Enabled automatically when using Java configuration or can be enabled explicitly via the regular <context:annotation-driven/> XML element. Detect methods of managed beans annotated with @eventlistener, either directly or through a meta-annotation. Annotated methods must define the event type they listen to as a single parameter argument. Events are automatically filtered out according to the method signature. When additional runtime filtering is required, one can specify the `condition` attribute of the annotation that defines a SpEL expression that should match to actually invoke the method for a particular event. The root context exposes the actual `event` (`#root.event`) and method arguments (`#root.args`). Individual method arguments are also exposed via either the `a` or `p` alias (`#a0` refers to the first method argument). Finally, methods arguments are exposed via their names if that information can be discovered. Events can be either an ApplicationEvent or any arbitrary payload. Such payload is wrapped automatically in a PayloadApplicationEvent and managed explicitly internally. As a result, users can now publish and listen for arbitrary objects. If an annotated method has a return value, an non null result is actually published as a new event, something like: @eventlistener public FooEvent handle(BarEvent event) { ... } Events can be handled in an aynchronous manner by adding `@Async` to the event method declaration and enabling such infrastructure. Events can also be ordered by adding an `@Order` annotation to the event method. Issue: SPR-11622
1 parent 6d6422a commit f0fca89

31 files changed

+2288
-134
lines changed

spring-context/src/main/java/org/springframework/cache/interceptor/CacheEvaluationContext.java

Lines changed: 6 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,9 @@
1919
import java.lang.reflect.Method;
2020
import java.util.ArrayList;
2121
import java.util.List;
22-
import java.util.Map;
2322

24-
import org.springframework.aop.support.AopUtils;
25-
import org.springframework.context.expression.AnnotatedElementKey;
23+
import org.springframework.context.expression.MethodBasedEvaluationContext;
2624
import org.springframework.core.ParameterNameDiscoverer;
27-
import org.springframework.expression.spel.support.StandardEvaluationContext;
28-
import org.springframework.util.ObjectUtils;
2925

3026
/**
3127
* Cache specific evaluation context that adds a method parameters as SpEL
@@ -44,32 +40,14 @@
4440
* @author Stephane Nicoll
4541
* @since 3.1
4642
*/
47-
class CacheEvaluationContext extends StandardEvaluationContext {
48-
49-
private final ParameterNameDiscoverer paramDiscoverer;
50-
51-
private final Method method;
52-
53-
private final Object[] args;
54-
55-
private final Class<?> targetClass;
56-
57-
private final Map<AnnotatedElementKey, Method> methodCache;
43+
class CacheEvaluationContext extends MethodBasedEvaluationContext {
5844

5945
private final List<String> unavailableVariables;
6046

61-
private boolean paramLoaded = false;
47+
CacheEvaluationContext(Object rootObject, Method method, Object[] args,
48+
ParameterNameDiscoverer paramDiscoverer) {
6249

63-
64-
CacheEvaluationContext(Object rootObject, ParameterNameDiscoverer paramDiscoverer, Method method,
65-
Object[] args, Class<?> targetClass, Map<AnnotatedElementKey, Method> methodCache) {
66-
super(rootObject);
67-
68-
this.paramDiscoverer = paramDiscoverer;
69-
this.method = method;
70-
this.args = args;
71-
this.targetClass = targetClass;
72-
this.methodCache = methodCache;
50+
super(rootObject, method, args, paramDiscoverer);
7351
this.unavailableVariables = new ArrayList<String>();
7452
}
7553

@@ -93,47 +71,7 @@ public Object lookupVariable(String name) {
9371
if (this.unavailableVariables.contains(name)) {
9472
throw new VariableNotAvailableException(name);
9573
}
96-
Object variable = super.lookupVariable(name);
97-
if (variable != null) {
98-
return variable;
99-
}
100-
if (!this.paramLoaded) {
101-
loadArgsAsVariables();
102-
this.paramLoaded = true;
103-
variable = super.lookupVariable(name);
104-
}
105-
return variable;
106-
}
107-
108-
private void loadArgsAsVariables() {
109-
// shortcut if no args need to be loaded
110-
if (ObjectUtils.isEmpty(this.args)) {
111-
return;
112-
}
113-
114-
AnnotatedElementKey methodKey = new AnnotatedElementKey(this.method, this.targetClass);
115-
Method targetMethod = this.methodCache.get(methodKey);
116-
if (targetMethod == null) {
117-
targetMethod = AopUtils.getMostSpecificMethod(this.method, this.targetClass);
118-
if (targetMethod == null) {
119-
targetMethod = this.method;
120-
}
121-
this.methodCache.put(methodKey, targetMethod);
122-
}
123-
124-
// save arguments as indexed variables
125-
for (int i = 0; i < this.args.length; i++) {
126-
setVariable("a" + i, this.args[i]);
127-
setVariable("p" + i, this.args[i]);
128-
}
129-
130-
String[] parameterNames = this.paramDiscoverer.getParameterNames(targetMethod);
131-
// save parameter names (if discovered)
132-
if (parameterNames != null) {
133-
for (int i = 0; i < parameterNames.length; i++) {
134-
setVariable(parameterNames[i], this.args[i]);
135-
}
136-
}
74+
return super.lookupVariable(name);
13775
}
13876

13977
}

spring-context/src/main/java/org/springframework/cache/interceptor/ExpressionEvaluator.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Map;
2222
import java.util.concurrent.ConcurrentHashMap;
2323

24+
import org.springframework.aop.support.AopUtils;
2425
import org.springframework.cache.Cache;
2526
import org.springframework.context.expression.AnnotatedElementKey;
2627
import org.springframework.context.expression.CachedExpressionEvaluator;
@@ -98,8 +99,9 @@ public EvaluationContext createEvaluationContext(Collection<? extends Cache> cac
9899

99100
CacheExpressionRootObject rootObject = new CacheExpressionRootObject(caches,
100101
method, args, target, targetClass);
102+
Method targetMethod = getTargetMethod(targetClass, method);
101103
CacheEvaluationContext evaluationContext = new CacheEvaluationContext(rootObject,
102-
this.paramNameDiscoverer, method, args, targetClass, this.targetMethodCache);
104+
targetMethod, args, this.paramNameDiscoverer);
103105
if (result == RESULT_UNAVAILABLE) {
104106
evaluationContext.addUnavailableVariable(RESULT_VARIABLE);
105107
}
@@ -121,5 +123,18 @@ public boolean unless(String unlessExpression, AnnotatedElementKey methodKey, Ev
121123
return getExpression(this.unlessCache, methodKey, unlessExpression).getValue(evalContext, boolean.class);
122124
}
123125

126+
private Method getTargetMethod(Class<?> targetClass, Method method) {
127+
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
128+
Method targetMethod = this.targetMethodCache.get(methodKey);
129+
if (targetMethod == null) {
130+
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
131+
if (targetMethod == null) {
132+
targetMethod = method;
133+
}
134+
this.targetMethodCache.put(methodKey, targetMethod);
135+
}
136+
return targetMethod;
137+
}
138+
124139

125140
}

spring-context/src/main/java/org/springframework/context/ApplicationEventPublisher.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
* Serves as super-interface for ApplicationContext.
2222
*
2323
* @author Juergen Hoeller
24+
* @author Stephane Nicoll
2425
* @since 1.1.1
2526
* @see ApplicationContext
2627
* @see ApplicationEventPublisherAware
@@ -38,4 +39,14 @@ public interface ApplicationEventPublisher {
3839
*/
3940
void publishEvent(ApplicationEvent event);
4041

42+
/**
43+
* Notify all <strong>matching</strong> listeners registered with this
44+
* application of an event.
45+
* <p>If the specified {@code event} is not an {@link ApplicationEvent}, it
46+
* is wrapped in a {@code GenericApplicationEvent}.
47+
* @param event the event to publish
48+
* @see PayloadApplicationEvent
49+
*/
50+
void publishEvent(Object event);
51+
4152
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context;
18+
19+
import org.springframework.util.Assert;
20+
21+
/**
22+
* An {@link ApplicationEvent} that carries an arbitrary payload.
23+
* <p>
24+
* Mainly intended for internal use within the framework.
25+
*
26+
* @param <T> the payload type of the event
27+
* @author Stephane Nicoll
28+
* @since 4.2
29+
*/
30+
@SuppressWarnings("serial")
31+
public class PayloadApplicationEvent<T>
32+
extends ApplicationEvent {
33+
34+
private final T payload;
35+
36+
public PayloadApplicationEvent(Object source, T payload) {
37+
super(source);
38+
Assert.notNull(payload, "Payload must not be null");
39+
this.payload = payload;
40+
}
41+
42+
/**
43+
* Return the payload of the event.
44+
*/
45+
public T getPayload() {
46+
return payload;
47+
}
48+
49+
}
50+

spring-context/src/main/java/org/springframework/context/annotation/AnnotationConfigUtils.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
3030
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3131
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3232
import org.springframework.beans.factory.support.RootBeanDefinition;
33+
import org.springframework.context.event.EventListenerMethodProcessor;
3334
import org.springframework.context.support.GenericApplicationContext;
3435
import org.springframework.core.annotation.AnnotationAttributes;
3536
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
@@ -48,6 +49,7 @@
4849
* @author Juergen Hoeller
4950
* @author Chris Beams
5051
* @author Phillip Webb
52+
* @author Stephane Nicoll
5153
* @since 2.5
5254
* @see ContextAnnotationAutowireCandidateResolver
5355
* @see CommonAnnotationBeanPostProcessor
@@ -103,6 +105,11 @@ public class AnnotationConfigUtils {
103105
private static final String PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME =
104106
"org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor";
105107

108+
/**
109+
* The bean name of the internally managed @EventListener annotation processor.
110+
*/
111+
public static final String EVENT_LISTENER_PROCESSOR_BEAN_NAME =
112+
"org.springframework.context.event.internalEventListenerProcessor";
106113

107114
private static final boolean jsr250Present =
108115
ClassUtils.isPresent("javax.annotation.Resource", AnnotationConfigUtils.class.getClassLoader());
@@ -183,6 +190,12 @@ public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
183190
beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
184191
}
185192

193+
if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
194+
RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
195+
def.setSource(source);
196+
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
197+
}
198+
186199
return beanDefs;
187200
}
188201

0 commit comments

Comments
 (0)