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
This commit is contained in:
Stephane Nicoll
2015-02-02 10:46:28 +01:00
parent f0fca890bb
commit 4741a12fdc
17 changed files with 1161 additions and 34 deletions

View File

@@ -30,6 +30,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.event.DefaultEventListenerFactory;
import org.springframework.context.event.EventListenerMethodProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.AnnotationAttributes;
@@ -111,6 +112,12 @@ public class AnnotationConfigUtils {
public static final String EVENT_LISTENER_PROCESSOR_BEAN_NAME =
"org.springframework.context.event.internalEventListenerProcessor";
/**
* The bean name of the internally managed EventListenerFactory.
*/
public static final String EVENT_LISTENER_FACTORY_BEAN_NAME =
"org.springframework.context.event.internalEventListenerFactory";
private static final boolean jsr250Present =
ClassUtils.isPresent("javax.annotation.Resource", AnnotationConfigUtils.class.getClassLoader());
@@ -195,6 +202,11 @@ public class AnnotationConfigUtils {
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
}
return beanDefs;
}

View File

@@ -16,6 +16,7 @@
package org.springframework.context.event;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@@ -30,6 +31,8 @@ import org.springframework.context.PayloadApplicationEvent;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.core.BridgeMethodResolver;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.expression.EvaluationContext;
@@ -41,11 +44,11 @@ import org.springframework.util.StringUtils;
* {@link GenericApplicationListener} adapter that delegates the processing of
* an event to an {@link EventListener} annotated method.
*
* <p>Unwrap the content of a {@link PayloadApplicationEvent} if necessary
* to allow method declaration to define any arbitrary event type.
*
* <p>If a condition is defined, it is evaluated prior to invoking the
* underlying method.
* <p>Delegates to {@link #processEvent(ApplicationEvent)} to give a chance to
* sub-classes to deviate from the default. Unwrap the content of a
* {@link PayloadApplicationEvent} if necessary to allow method declaration
* to define any arbitrary event type. If a condition is defined, it is
* evaluated prior to invoking the underlying method.
*
* @author Stephane Nicoll
* @since 4.2
@@ -70,6 +73,8 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
private EventExpressionEvaluator evaluator;
private String condition;
public ApplicationListenerMethodAdapter(String beanName, Class<?> targetClass, Method method) {
this.beanName = beanName;
this.method = method;
@@ -89,6 +94,14 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
@Override
public void onApplicationEvent(ApplicationEvent event) {
processEvent(event);
}
/**
* Process the specified {@link ApplicationEvent}, checking if the condition
* match and handling non-null result, if any.
*/
public void processEvent(ApplicationEvent event) {
Object[] args = resolveArguments(event);
if (shouldHandle(event, args)) {
Object result = doInvoke(args);
@@ -131,8 +144,7 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
if (args == null) {
return false;
}
EventListener eventListener = AnnotationUtils.findAnnotation(this.method, EventListener.class);
String condition = (eventListener != null ? eventListener.condition() : null);
String condition = getCondition();
if (StringUtils.hasText(condition)) {
Assert.notNull(this.evaluator, "Evaluator must no be null.");
EvaluationContext evaluationContext = this.evaluator.createEvaluationContext(event,
@@ -161,10 +173,14 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
@Override
public int getOrder() {
Order order = AnnotationUtils.findAnnotation(this.method, Order.class);
Order order = getMethodAnnotation(Order.class);
return (order != null ? order.value() : 0);
}
protected <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) {
return AnnotationUtils.findAnnotation(this.method, annotationType);
}
/**
* Invoke the event listener method with the given argument values.
*/
@@ -202,6 +218,26 @@ public class ApplicationListenerMethodAdapter implements GenericApplicationListe
return this.applicationContext.getBean(this.beanName);
}
/**
* Return the condition to use. Matches the {@code condition} attribute of the
* {@link EventListener} annotation or any matching attribute on a meta-annotation.
*/
protected String getCondition() {
if (this.condition == null) {
AnnotationAttributes annotationAttributes = AnnotatedElementUtils
.getAnnotationAttributes(this.method, EventListener.class.getName());
if (annotationAttributes != null) {
String value = annotationAttributes.getString("condition");
this.condition = (value != null ? value : "");
}
else { // TODO annotationAttributes null with proxy
EventListener eventListener = getMethodAnnotation(EventListener.class);
this.condition = (eventListener != null ? eventListener.condition() : null);
}
}
return this.condition;
}
/**
* Add additional details such as the bean type and method signature to
* the given error message.

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.reflect.Method;
import org.springframework.context.ApplicationListener;
import org.springframework.core.Ordered;
/**
* Default {@link EventListenerFactory} implementation that supports the
* regular {@link EventListener} annotation.
* <p>Used as "catch-all" implementation by default.
*
* @author Stephane Nicoll
* @since 4.2
*/
public class DefaultEventListenerFactory implements EventListenerFactory, Ordered {
private int order = LOWEST_PRECEDENCE;
@Override
public int getOrder() {
return order;
}
public void setOrder(int order) {
this.order = order;
}
public boolean supportsMethod(Method method) {
return true;
}
@Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
return new ApplicationListenerMethodAdapter(beanName, type, method);
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright 2002-2015 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.event;
import java.lang.reflect.Method;
import org.springframework.context.ApplicationListener;
/**
* Strategy interface for creating {@link ApplicationListener} for methods
* annotated with {@link EventListener}.
*
* @author Stephane Nicoll
* @since 4.2
*/
public interface EventListenerFactory {
/**
* Specify if this factory supports the specified {@link Method}.
* @param method an {@link EventListener} annotated method
* @return {@code true} if this factory supports the specified method
*/
boolean supportsMethod(Method method);
/**
* Create an {@link ApplicationListener} for the specified method.
* @param beanName the name of the bean
* @param type the target type of the instance
* @param method the {@link EventListener} annotated method
* @return an application listener, suitable to invoke the specified method
*/
ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method);
}

View File

@@ -17,8 +17,11 @@
package org.springframework.context.event;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
@@ -35,6 +38,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
@@ -65,14 +69,27 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
}
/**
* Return the {@link EventListenerFactory} instances to use to handle {@link EventListener}
* annotated methods.
*/
protected List<EventListenerFactory> getEventListenerFactories() {
Map<String, EventListenerFactory> beans =
this.applicationContext.getBeansOfType(EventListenerFactory.class);
List<EventListenerFactory> allFactories = new ArrayList<EventListenerFactory>(beans.values());
AnnotationAwareOrderComparator.sort(allFactories);
return allFactories;
}
@Override
public void afterSingletonsInstantiated() {
List<EventListenerFactory> factories = getEventListenerFactories();
String[] allBeanNames = this.applicationContext.getBeanNamesForType(Object.class);
for (String beanName : allBeanNames) {
if (!ScopedProxyUtils.isScopedTarget(beanName)) {
Class<?> type = this.applicationContext.getType(beanName);
try {
processBean(beanName, type);
processBean(factories, beanName, type);
}
catch (RuntimeException e) {
throw new BeanInitializationException("Failed to process @EventListener " +
@@ -82,22 +99,31 @@ public class EventListenerMethodProcessor implements SmartInitializingSingleton,
}
}
protected void processBean(String beanName, final Class<?> type) {
protected void processBean(List<EventListenerFactory> factories, String beanName, final Class<?> type) {
Class<?> targetType = getTargetClass(beanName, type);
if (!this.nonAnnotatedClasses.contains(targetType)) {
final Set<Method> annotatedMethods = new LinkedHashSet<Method>(1);
Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(targetType);
for (Method method : methods) {
EventListener eventListener = AnnotationUtils.findAnnotation(method, EventListener.class);
if (eventListener != null) {
if (!type.equals(targetType)) {
method = getProxyMethod(type, method);
if (eventListener == null) {
continue;
}
for (EventListenerFactory factory : factories) {
if (factory.supportsMethod(method)) {
if (!type.equals(targetType)) {
method = getProxyMethod(type, method);
}
ApplicationListener<?> applicationListener =
factory.createApplicationListener(beanName, type, method);
if (applicationListener instanceof ApplicationListenerMethodAdapter) {
((ApplicationListenerMethodAdapter)applicationListener)
.init(this.applicationContext, this.evaluator);
}
this.applicationContext.addApplicationListener(applicationListener);
annotatedMethods.add(method);
break;
}
ApplicationListenerMethodAdapter applicationListener =
new ApplicationListenerMethodAdapter(beanName, type, method);
applicationListener.init(this.applicationContext, this.evaluator);
this.applicationContext.addApplicationListener(applicationListener);
annotatedMethods.add(method);
}
}
if (annotatedMethods.isEmpty()) {