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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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()) {
|
||||
|
||||
Reference in New Issue
Block a user