Skip to content

Commit 4741a12

Browse files
committed
Support for transactional event listener
Update the application event listener infrastructure to support events that are processed according to a transactional phase. Introduce EventListenerFactory that can be implemented to provide support for additional event listener types. TransactionalEventListener is a new annotation that can be used in lieu of the regular EventListener. Its related factory implementation is registered in the context automatically via @EnableTransactionManagement or <tx:annotation-driven/> By default, a TransactionalEventListener is invoked when the transaction has completed successfully (i.e. AFTER_COMMIT). Additional phases are provided to handle BEFORE_COMMIT and AFTER_ROLLBACK events. If no transaction is running, such listener is not invoked at all unless the `fallbackExecution` flag has been explicitly set. Issue: SPR-12080
1 parent f0fca89 commit 4741a12

17 files changed

+1161
-34
lines changed

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.DefaultEventListenerFactory;
3334
import org.springframework.context.event.EventListenerMethodProcessor;
3435
import org.springframework.context.support.GenericApplicationContext;
3536
import org.springframework.core.annotation.AnnotationAttributes;
@@ -111,6 +112,12 @@ public class AnnotationConfigUtils {
111112
public static final String EVENT_LISTENER_PROCESSOR_BEAN_NAME =
112113
"org.springframework.context.event.internalEventListenerProcessor";
113114

115+
/**
116+
* The bean name of the internally managed EventListenerFactory.
117+
*/
118+
public static final String EVENT_LISTENER_FACTORY_BEAN_NAME =
119+
"org.springframework.context.event.internalEventListenerFactory";
120+
114121
private static final boolean jsr250Present =
115122
ClassUtils.isPresent("javax.annotation.Resource", AnnotationConfigUtils.class.getClassLoader());
116123

@@ -195,6 +202,11 @@ public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
195202
def.setSource(source);
196203
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
197204
}
205+
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
206+
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
207+
def.setSource(source);
208+
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
209+
}
198210

199211
return beanDefs;
200212
}

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

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.context.event;
1818

19+
import java.lang.annotation.Annotation;
1920
import java.lang.reflect.InvocationTargetException;
2021
import java.lang.reflect.Method;
2122
import java.lang.reflect.Parameter;
@@ -30,6 +31,8 @@
3031
import org.springframework.context.expression.AnnotatedElementKey;
3132
import org.springframework.core.BridgeMethodResolver;
3233
import org.springframework.core.ResolvableType;
34+
import org.springframework.core.annotation.AnnotatedElementUtils;
35+
import org.springframework.core.annotation.AnnotationAttributes;
3336
import org.springframework.core.annotation.AnnotationUtils;
3437
import org.springframework.core.annotation.Order;
3538
import org.springframework.expression.EvaluationContext;
@@ -41,11 +44,11 @@
4144
* {@link GenericApplicationListener} adapter that delegates the processing of
4245
* an event to an {@link EventListener} annotated method.
4346
*
44-
* <p>Unwrap the content of a {@link PayloadApplicationEvent} if necessary
45-
* to allow method declaration to define any arbitrary event type.
46-
*
47-
* <p>If a condition is defined, it is evaluated prior to invoking the
48-
* underlying method.
47+
* <p>Delegates to {@link #processEvent(ApplicationEvent)} to give a chance to
48+
* sub-classes to deviate from the default. Unwrap the content of a
49+
* {@link PayloadApplicationEvent} if necessary to allow method declaration
50+
* to define any arbitrary event type. If a condition is defined, it is
51+
* evaluated prior to invoking the underlying method.
4952
*
5053
* @author Stephane Nicoll
5154
* @since 4.2
@@ -70,6 +73,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
7073

7174
private EventExpressionEvaluator evaluator;
7275

76+
private String condition;
77+
7378
public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
7479
this.beanName = beanName;
7580
this.method = method;
@@ -89,6 +94,14 @@ void init(ApplicationContext applicationContext, EventExpressionEvaluator evalua
8994

9095
@Override
9196
public void onApplicationEvent(ApplicationEvent event) {
97+
processEvent(event);
98+
}
99+
100+
/**
101+
* Process the specified {@link ApplicationEvent}, checking if the condition
102+
* match and handling non-null result, if any.
103+
*/
104+
public void processEvent(ApplicationEvent event) {
92105
Object[] args = resolveArguments(event);
93106
if (shouldHandle(event, args)) {
94107
Object result = doInvoke(args);
@@ -131,8 +144,7 @@ private boolean shouldHandle(ApplicationEvent event, Object[] args) {
131144
if (args == null) {
132145
return false;
133146
}
134-
EventListener eventListener = AnnotationUtils.findAnnotation(this.method, EventListener.class);
135-
String condition = (eventListener != null ? eventListener.condition() : null);
147+
String condition = getCondition();
136148
if (StringUtils.hasText(condition)) {
137149
Assert.notNull(this.evaluator, "Evaluator must no be null.");
138150
EvaluationContext evaluationContext = this.evaluator.createEvaluationContext(event,
@@ -161,10 +173,14 @@ public boolean supportsSourceType(Class<?> sourceType) {
161173

162174
@Override
163175
public int getOrder() {
164-
Order order = AnnotationUtils.findAnnotation(this.method, Order.class);
176+
Order order = getMethodAnnotation(Order.class);
165177
return (order != null ? order.value() : 0);
166178
}
167179

180+
protected <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
181+
return AnnotationUtils.findAnnotation(this.method, annotationType);
182+
}
183+
168184
/**
169185
* Invoke the event listener method with the given argument values.
170186
*/
@@ -202,6 +218,26 @@ protected Object getTargetBean() {
202218
return this.applicationContext.getBean(this.beanName);
203219
}
204220

221+
/**
222+
* Return the condition to use. Matches the {@code condition} attribute of the
223+
* {@link EventListener} annotation or any matching attribute on a meta-annotation.
224+
*/
225+
protected String getCondition() {
226+
if (this.condition == null) {
227+
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
228+
.getAnnotationAttributes(this.method, EventListener.class.getName());
229+
if (annotationAttributes != null) {
230+
String value = annotationAttributes.getString("condition");
231+
this.condition = (value != null ? value : "");
232+
}
233+
else { // TODO annotationAttributes null with proxy
234+
EventListener eventListener = getMethodAnnotation(EventListener.class);
235+
this.condition = (eventListener != null ? eventListener.condition() : null);
236+
}
237+
}
238+
return this.condition;
239+
}
240+
205241
/**
206242
* Add additional details such as the bean type and method signature to
207243
* the given error message.
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.event;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.springframework.context.ApplicationListener;
22+
import org.springframework.core.Ordered;
23+
24+
/**
25+
* Default {@link EventListenerFactory} implementation that supports the
26+
* regular {@link EventListener} annotation.
27+
* <p>Used as "catch-all" implementation by default.
28+
*
29+
* @author Stephane Nicoll
30+
* @since 4.2
31+
*/
32+
public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {
33+
34+
private int order = LOWEST_PRECEDENCE;
35+
36+
@Override
37+
public int getOrder() {
38+
return order;
39+
}
40+
41+
public void setOrder(int order) {
42+
this.order = order;
43+
}
44+
45+
public boolean supportsMethod(Method method) {
46+
return true;
47+
}
48+
49+
@Override
50+
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
51+
return new ApplicationListenerMethodAdapter(beanName, type, method);
52+
}
53+
54+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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.event;
18+
19+
import java.lang.reflect.Method;
20+
21+
import org.springframework.context.ApplicationListener;
22+
23+
/**
24+
* Strategy interface for creating {@link ApplicationListener} for methods
25+
* annotated with {@link EventListener}.
26+
*
27+
* @author Stephane Nicoll
28+
* @since 4.2
29+
*/
30+
public interface EventListenerFactory {
31+
32+
/**
33+
* Specify if this factory supports the specified {@link Method}.
34+
* @param method an {@link EventListener} annotated method
35+
* @return {@code true} if this factory supports the specified method
36+
*/
37+
boolean supportsMethod(Method method);
38+
39+
/**
40+
* Create an {@link ApplicationListener} for the specified method.
41+
* @param beanName the name of the bean
42+
* @param type the target type of the instance
43+
* @param method the {@link EventListener} annotated method
44+
* @return an application listener, suitable to invoke the specified method
45+
*/
46+
ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method);
47+
48+
}

spring-context/src/main/java/org/springframework/context/event/EventListenerMethodProcessor.java

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,11 @@
1717
package org.springframework.context.event;
1818

1919
import java.lang.reflect.Method;
20+
import java.util.ArrayList;
2021
import java.util.Collections;
2122
import java.util.LinkedHashSet;
23+
import java.util.List;
24+
import java.util.Map;
2225
import java.util.Set;
2326
import java.util.concurrent.ConcurrentHashMap;
2427

@@ -35,6 +38,7 @@
3538
import org.springframework.context.ApplicationContextAware;
3639
import org.springframework.context.ApplicationListener;
3740
import org.springframework.context.ConfigurableApplicationContext;
41+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3842
import org.springframework.core.annotation.AnnotationUtils;
3943
import org.springframework.util.Assert;
4044
import org.springframework.util.ReflectionUtils;
@@ -65,14 +69,27 @@ public void setApplicationContext(ApplicationContext applicationContext) throws
6569

6670
}
6771

72+
/**
73+
* Return the {@link EventListenerFactory} instances to use to handle {@link EventListener}
74+
* annotated methods.
75+
*/
76+
protected List<EventListenerFactory> getEventListenerFactories() {
77+
Map<String, EventListenerFactory> beans =
78+
this.applicationContext.getBeansOfType(EventListenerFactory.class);
79+
List<EventListenerFactory> allFactories = new ArrayList<EventListenerFactory>(beans.values());
80+
AnnotationAwareOrderComparator.sort(allFactories);
81+
return allFactories;
82+
}
83+
6884
@Override
6985
public void afterSingletonsInstantiated() {
86+
List<EventListenerFactory> factories = getEventListenerFactories();
7087
String[] allBeanNames = this.applicationContext.getBeanNamesForType(Object.class);
7188
for (String beanName : allBeanNames) {
7289
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
7390
Class<?> type = this.applicationContext.getType(beanName);
7491
try {
75-
processBean(beanName, type);
92+
processBean(factories, beanName, type);
7693
}
7794
catch (RuntimeException e) {
7895
throw new BeanInitializationException("Failed to process @EventListener " +
@@ -82,22 +99,31 @@ public void afterSingletonsInstantiated() {
8299
}
83100
}
84101

85-
protected void processBean(String beanName, final Class<?> type) {
102+
protected void processBean(List<EventListenerFactory> factories, String beanName, final Class<?> type) {
86103
Class<?> targetType = getTargetClass(beanName, type);
87104
if (!this.nonAnnotatedClasses.contains(targetType)) {
88105
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
89106
Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(targetType);
90107
for (Method method : methods) {
91108
EventListener eventListener = AnnotationUtils.findAnnotation(method, EventListener.class);
92-
if (eventListener != null) {
93-
if (!type.equals(targetType)) {
94-
method = getProxyMethod(type, method);
109+
if (eventListener == null) {
110+
continue;
111+
}
112+
for (EventListenerFactory factory : factories) {
113+
if (factory.supportsMethod(method)) {
114+
if (!type.equals(targetType)) {
115+
method = getProxyMethod(type, method);
116+
}
117+
ApplicationListener<?> applicationListener =
118+
factory.createApplicationListener(beanName, type, method);
119+
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
120+
((ApplicationListenerMethodAdapter)applicationListener)
121+
.init(this.applicationContext, this.evaluator);
122+
}
123+
this.applicationContext.addApplicationListener(applicationListener);
124+
annotatedMethods.add(method);
125+
break;
95126
}
96-
ApplicationListenerMethodAdapter applicationListener =
97-
new ApplicationListenerMethodAdapter(beanName, type, method);
98-
applicationListener.init(this.applicationContext, this.evaluator);
99-
this.applicationContext.addApplicationListener(applicationListener);
100-
annotatedMethods.add(method);
101127
}
102128
}
103129
if (annotatedMethods.isEmpty()) {

0 commit comments

Comments
 (0)