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:
parent
f0fca890bb
commit
4741a12fdc
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(11, beanCount);
|
||||
assertEquals(12, beanCount);
|
||||
assertTrue(context.containsBean("serviceInvocationCounter"));
|
||||
assertTrue(context.containsBean("fooServiceImpl"));
|
||||
assertTrue(context.containsBean("stubFooDao"));
|
||||
|
|
@ -67,6 +67,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
context.refresh();
|
||||
FooServiceImpl service = context.getBean("fooServiceImpl", FooServiceImpl.class);
|
||||
assertTrue(context.getDefaultListableBeanFactory().containsSingleton("myNamedComponent"));
|
||||
|
|
@ -99,7 +100,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
GenericApplicationContext context = new GenericApplicationContext();
|
||||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(11, beanCount);
|
||||
assertEquals(12, beanCount);
|
||||
scanner.scan(BASE_PACKAGE);
|
||||
assertTrue(context.containsBean("serviceInvocationCounter"));
|
||||
assertTrue(context.containsBean("fooServiceImpl"));
|
||||
|
|
@ -219,12 +220,13 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(6, beanCount);
|
||||
assertEquals(7, beanCount);
|
||||
assertTrue(context.containsBean("messageBean"));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -233,7 +235,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, false);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(6, beanCount);
|
||||
assertEquals(7, beanCount);
|
||||
assertTrue(context.containsBean("messageBean"));
|
||||
assertFalse(context.containsBean("serviceInvocationCounter"));
|
||||
assertFalse(context.containsBean("fooServiceImpl"));
|
||||
|
|
@ -244,6 +246,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -252,7 +255,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
|
||||
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomComponent.class));
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(12, beanCount);
|
||||
assertEquals(13, beanCount);
|
||||
assertTrue(context.containsBean("messageBean"));
|
||||
assertTrue(context.containsBean("serviceInvocationCounter"));
|
||||
assertTrue(context.containsBean("fooServiceImpl"));
|
||||
|
|
@ -263,6 +266,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -271,7 +275,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
|
||||
scanner.addExcludeFilter(new AnnotationTypeFilter(Aspect.class));
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(10, beanCount);
|
||||
assertEquals(11, beanCount);
|
||||
assertFalse(context.containsBean("serviceInvocationCounter"));
|
||||
assertTrue(context.containsBean("fooServiceImpl"));
|
||||
assertTrue(context.containsBean("stubFooDao"));
|
||||
|
|
@ -289,7 +293,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context, true);
|
||||
scanner.addExcludeFilter(new AssignableTypeFilter(FooService.class));
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(10, beanCount);
|
||||
assertEquals(11, beanCount);
|
||||
assertFalse(context.containsBean("fooServiceImpl"));
|
||||
assertTrue(context.containsBean("serviceInvocationCounter"));
|
||||
assertTrue(context.containsBean("stubFooDao"));
|
||||
|
|
@ -299,6 +303,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -326,7 +331,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
scanner.addExcludeFilter(new AssignableTypeFilter(FooService.class));
|
||||
scanner.addExcludeFilter(new AnnotationTypeFilter(Aspect.class));
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(9, beanCount);
|
||||
assertEquals(10, beanCount);
|
||||
assertFalse(context.containsBean("fooServiceImpl"));
|
||||
assertFalse(context.containsBean("serviceInvocationCounter"));
|
||||
assertTrue(context.containsBean("stubFooDao"));
|
||||
|
|
@ -336,6 +341,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -344,7 +350,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(11, beanCount);
|
||||
assertEquals(12, beanCount);
|
||||
assertFalse(context.containsBean("fooServiceImpl"));
|
||||
assertTrue(context.containsBean("fooService"));
|
||||
assertTrue(context.containsBean("serviceInvocationCounter"));
|
||||
|
|
@ -355,6 +361,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
assertTrue(context.containsBean(AnnotationConfigUtils.REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_PROCESSOR_BEAN_NAME));
|
||||
assertTrue(context.containsBean(AnnotationConfigUtils.EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -364,7 +371,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
GenericApplicationContext multiPackageContext = new GenericApplicationContext();
|
||||
ClassPathBeanDefinitionScanner multiPackageScanner = new ClassPathBeanDefinitionScanner(multiPackageContext);
|
||||
int singlePackageBeanCount = singlePackageScanner.scan(BASE_PACKAGE);
|
||||
assertEquals(11, singlePackageBeanCount);
|
||||
assertEquals(12, singlePackageBeanCount);
|
||||
multiPackageScanner.scan(BASE_PACKAGE, "org.springframework.dao.annotation");
|
||||
// assertTrue(multiPackageBeanCount > singlePackageBeanCount);
|
||||
}
|
||||
|
|
@ -375,7 +382,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
int initialBeanCount = context.getBeanDefinitionCount();
|
||||
int scannedBeanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(11, scannedBeanCount);
|
||||
assertEquals(12, scannedBeanCount);
|
||||
assertEquals(scannedBeanCount, context.getBeanDefinitionCount() - initialBeanCount);
|
||||
int addedBeanCount = scanner.scan("org.springframework.aop.aspectj.annotation");
|
||||
assertEquals(initialBeanCount + scannedBeanCount + addedBeanCount, context.getBeanDefinitionCount());
|
||||
|
|
@ -388,7 +395,7 @@ public class ClassPathBeanDefinitionScannerTests {
|
|||
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context);
|
||||
scanner.setBeanNameGenerator(new TestBeanNameGenerator());
|
||||
int beanCount = scanner.scan(BASE_PACKAGE);
|
||||
assertEquals(11, beanCount);
|
||||
assertEquals(12, beanCount);
|
||||
context.refresh();
|
||||
|
||||
FooServiceImpl fooService = context.getBean("fooService", FooServiceImpl.class);
|
||||
|
|
|
|||
|
|
@ -19,11 +19,16 @@ package org.springframework.transaction.annotation;
|
|||
import java.util.Collection;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.context.annotation.Role;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
import org.springframework.transaction.event.TransactionalEventListenerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
|
|
@ -32,6 +37,7 @@ import org.springframework.util.CollectionUtils;
|
|||
* Spring's annotation-driven transaction management capability.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.1
|
||||
* @see EnableTransactionManagement
|
||||
*/
|
||||
|
|
@ -46,6 +52,12 @@ public abstract class AbstractTransactionManagementConfiguration implements Impo
|
|||
protected PlatformTransactionManager txManager;
|
||||
|
||||
|
||||
@Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public TransactionalEventListenerFactory transactionalEventListenerFactory() {
|
||||
return new TransactionalEventListenerFactory();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata importMetadata) {
|
||||
this.enableTx = AnnotationAttributes.fromMap(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -26,6 +26,7 @@ import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
|
|||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.beans.factory.xml.BeanDefinitionParser;
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
import org.springframework.transaction.event.TransactionalEventListenerFactory;
|
||||
import org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor;
|
||||
import org.springframework.transaction.interceptor.TransactionInterceptor;
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ import org.springframework.transaction.interceptor.TransactionInterceptor;
|
|||
* @author Juergen Hoeller
|
||||
* @author Rob Harrop
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
* @since 2.0
|
||||
*/
|
||||
class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
||||
|
|
@ -55,6 +57,7 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
*/
|
||||
@Override
|
||||
public BeanDefinition parse(Element element, ParserContext parserContext) {
|
||||
registerTransactionalEventListenerFactory(parserContext);
|
||||
String mode = element.getAttribute("mode");
|
||||
if ("aspectj".equals(mode)) {
|
||||
// mode="aspectj"
|
||||
|
|
@ -84,6 +87,13 @@ class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
|
|||
TxNamespaceHandler.getTransactionManagerName(element));
|
||||
}
|
||||
|
||||
private void registerTransactionalEventListenerFactory(ParserContext parserContext) {
|
||||
RootBeanDefinition def = new RootBeanDefinition();
|
||||
def.setBeanClass(TransactionalEventListenerFactory.class);
|
||||
parserContext.registerBeanComponent(new BeanComponentDefinition(def,
|
||||
TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Inner class to just introduce an AOP framework dependency when actually in proxy mode.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -20,6 +20,7 @@ package org.springframework.transaction.config;
|
|||
* Configuration constants for internal sharing across subpackages.
|
||||
*
|
||||
* @author Chris Beams
|
||||
* @author Stephane Nicoll
|
||||
* @since 3.1
|
||||
*/
|
||||
public abstract class TransactionManagementConfigUtils {
|
||||
|
|
@ -48,4 +49,10 @@ public abstract class TransactionManagementConfigUtils {
|
|||
public static final String TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME =
|
||||
"org.springframework.transaction.aspectj.AspectJTransactionManagementConfiguration";
|
||||
|
||||
/**
|
||||
* The bean name of the internally managed TransactionalEventListenerFactory.
|
||||
*/
|
||||
public static final String TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME =
|
||||
"org.springframework.transaction.config.internalTransactionalEventListenerFactory";
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* 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.transaction.event;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.event.ApplicationListenerMethodAdapter;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.context.event.GenericApplicationListener;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
/**
|
||||
* {@link GenericApplicationListener} adapter that delegates the processing of
|
||||
* an event to a {@link TransactionalEventListener} annotated method. Supports
|
||||
* the exact same features as any regular {@link EventListener} annotated method
|
||||
* but is aware of the transactional context of the event publisher.
|
||||
* <p>
|
||||
* Processing of {@link TransactionalEventListener} is enabled automatically when
|
||||
* Spring's transaction management is enabled. For other cases, registering a
|
||||
* bean of type {@link TransactionalEventListenerFactory} is required.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.2
|
||||
* @see ApplicationListenerMethodAdapter
|
||||
* @see TransactionalEventListener
|
||||
*/
|
||||
class ApplicationListenerMethodTransactionalAdapter extends ApplicationListenerMethodAdapter {
|
||||
|
||||
protected final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final TransactionalEventListener annotation;
|
||||
|
||||
public ApplicationListenerMethodTransactionalAdapter(String beanName, Class<?> targetClass, Method method) {
|
||||
super(beanName, targetClass, method);
|
||||
this.annotation = findAnnotation(method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onApplicationEvent(ApplicationEvent event) {
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
|
||||
TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
|
||||
}
|
||||
else if (annotation.fallbackExecution()) {
|
||||
if (annotation.phase() == TransactionPhase.AFTER_ROLLBACK) {
|
||||
logger.warn("Processing '" + event + "' as a fallback execution on AFTER_ROLLBACK phase.");
|
||||
}
|
||||
processEvent(event);
|
||||
}
|
||||
else {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("No transaction is running, skipping '" + event + "' for '" + this + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private TransactionSynchronization createTransactionSynchronization(ApplicationEvent event) {
|
||||
return new TransactionSynchronizationEventAdapter(this, event, this.annotation.phase());
|
||||
}
|
||||
|
||||
static TransactionalEventListener findAnnotation(Method method) {
|
||||
TransactionalEventListener annotation = AnnotationUtils
|
||||
.findAnnotation(method, TransactionalEventListener.class);
|
||||
if (annotation == null) {
|
||||
throw new IllegalStateException("No TransactionalEventListener annotation found ou '" + method + "'");
|
||||
}
|
||||
return annotation;
|
||||
}
|
||||
|
||||
|
||||
private static class TransactionSynchronizationEventAdapter extends TransactionSynchronizationAdapter {
|
||||
|
||||
private final ApplicationListenerMethodAdapter listener;
|
||||
|
||||
private final ApplicationEvent event;
|
||||
|
||||
private final TransactionPhase phase;
|
||||
|
||||
protected TransactionSynchronizationEventAdapter(ApplicationListenerMethodAdapter listener,
|
||||
ApplicationEvent event, TransactionPhase phase) {
|
||||
|
||||
this.listener = listener;
|
||||
this.event = event;
|
||||
this.phase = phase;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
if (phase == TransactionPhase.BEFORE_COMMIT) {
|
||||
processEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterCompletion(int status) {
|
||||
if (phase == TransactionPhase.AFTER_COMPLETION) {
|
||||
processEvent();
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_COMMIT && status == STATUS_COMMITTED) {
|
||||
processEvent();
|
||||
}
|
||||
else if (phase == TransactionPhase.AFTER_ROLLBACK && status == STATUS_ROLLED_BACK) {
|
||||
processEvent();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return this.listener.getOrder();
|
||||
}
|
||||
|
||||
protected void processEvent() {
|
||||
this.listener.processEvent(this.event);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* 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.transaction.event;
|
||||
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
|
||||
/**
|
||||
* The phase at which a transactional event listener applies.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.2
|
||||
* @see TransactionalEventListener
|
||||
*/
|
||||
public enum TransactionPhase {
|
||||
|
||||
/**
|
||||
* Fire the event before transaction commit.
|
||||
* @see TransactionSynchronization#beforeCommit(boolean)
|
||||
*/
|
||||
BEFORE_COMMIT,
|
||||
|
||||
/**
|
||||
* Fire the event after the transaction has completed. For
|
||||
* more fine-grained event, use {@link #AFTER_COMMIT} or
|
||||
* {@link #AFTER_ROLLBACK} to intercept transaction commit
|
||||
* or rollback respectively.
|
||||
* @see TransactionSynchronization#afterCompletion(int)
|
||||
*/
|
||||
AFTER_COMPLETION,
|
||||
|
||||
/**
|
||||
* Fire the event after the commit has completed successfully.
|
||||
* @see TransactionSynchronization#afterCompletion(int)
|
||||
* @see TransactionSynchronization#STATUS_COMMITTED
|
||||
*/
|
||||
AFTER_COMMIT,
|
||||
|
||||
/**
|
||||
* Fire the event if the transaction has rolled back.
|
||||
* @see TransactionSynchronization#afterCompletion(int)
|
||||
* @see TransactionSynchronization#STATUS_ROLLED_BACK
|
||||
*/
|
||||
AFTER_ROLLBACK
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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.transaction.event;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.event.EventListener;
|
||||
|
||||
/**
|
||||
* An {@link EventListener} that is invoked according to a {@link TransactionPhase}.
|
||||
* <p>
|
||||
* If the event is not published in the boundaries of a managed transaction, the event
|
||||
* is discarded unless the {@link #fallbackExecution()} flag is explicitly set. If a
|
||||
* transaction is running, the event is processed according to its {@link TransactionPhase}.
|
||||
* <p>
|
||||
* Adding {@link org.springframework.core.annotation.Order @Order} on your annotated method
|
||||
* allows you to prioritize that listener amongst other listeners running on the same phase.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.2
|
||||
*/
|
||||
@EventListener
|
||||
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface TransactionalEventListener {
|
||||
|
||||
/**
|
||||
* Phase to bind the handling of an event to. If no transaction is in progress, the
|
||||
* event is not processed at all unless {@link #fallbackExecution()} has been
|
||||
* enabled explicitly.
|
||||
*/
|
||||
TransactionPhase phase() default TransactionPhase.AFTER_COMMIT;
|
||||
|
||||
/**
|
||||
* Specify if the event should be processed if no transaction is running.
|
||||
*/
|
||||
boolean fallbackExecution() default false;
|
||||
|
||||
/**
|
||||
* Spring Expression Language (SpEL) attribute used for conditioning the event handling.
|
||||
* <p>Default is "", meaning the event is always handled.
|
||||
* @see EventListener#condition()
|
||||
*/
|
||||
String condition() default "";
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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.transaction.event;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.EventListenerFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
|
||||
/**
|
||||
* {@link EventListenerFactory} implementation that handles {@link TransactionalEventListener}
|
||||
* annotated method.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 4.2
|
||||
*/
|
||||
public class TransactionalEventListenerFactory implements EventListenerFactory, Ordered {
|
||||
|
||||
private int order = 50;
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsMethod(Method method) {
|
||||
return AnnotationUtils.findAnnotation(method, TransactionalEventListener.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
|
||||
return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -31,6 +31,8 @@ import org.springframework.jmx.export.annotation.ManagedOperation;
|
|||
import org.springframework.jmx.export.annotation.ManagedResource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.tests.transaction.CallCountingTransactionManager;
|
||||
import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
import org.springframework.transaction.event.TransactionalEventListenerFactory;
|
||||
|
||||
/**
|
||||
* @author Rob Harrop
|
||||
|
|
@ -99,6 +101,12 @@ public class AnnotationTransactionNamespaceHandlerTests extends TestCase {
|
|||
server.invoke(ObjectName.getInstance("test:type=TestBean"), "doSomething", new Object[0], new String[0]));
|
||||
}
|
||||
|
||||
public void testTransactionalEventListenerRegisteredProperly() {
|
||||
assertTrue(this.context.containsBean(TransactionManagementConfigUtils
|
||||
.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
assertEquals(1, this.context.getBeansOfType(TransactionalEventListenerFactory.class).size());
|
||||
}
|
||||
|
||||
private TransactionalTestBean getTestBean() {
|
||||
return (TransactionalTestBean) context.getBean("testBean");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2002-2014 the original author or authors.
|
||||
* 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.
|
||||
|
|
@ -32,6 +32,8 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.tests.transaction.CallCountingTransactionManager;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.annotation.AnnotationTransactionNamespaceHandlerTests.TransactionalTestBean;
|
||||
import org.springframework.transaction.config.TransactionManagementConfigUtils;
|
||||
import org.springframework.transaction.event.TransactionalEventListenerFactory;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
|
@ -105,6 +107,16 @@ public class EnableTransactionManagementTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transactionalEventListenerRegisteredProperly() {
|
||||
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
|
||||
ctx.register(EnableTxConfig.class);
|
||||
ctx.refresh();
|
||||
assertTrue(ctx.containsBean(TransactionManagementConfigUtils
|
||||
.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME));
|
||||
assertEquals(1, ctx.getBeansOfType(TransactionalEventListenerFactory.class).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void spr11915() {
|
||||
AnnotationConfigApplicationContext ctx =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* 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.transaction.event;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class ApplicationListenerMethodTransactionalAdapterTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void noAnnotation() {
|
||||
Method m = ReflectionUtils.findMethod(PhaseConfigurationTestListener.class,
|
||||
"noAnnotation", String.class);
|
||||
|
||||
thrown.expect(IllegalStateException.class);
|
||||
thrown.expectMessage("noAnnotation");
|
||||
ApplicationListenerMethodTransactionalAdapter.findAnnotation(m);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultPhase() {
|
||||
Method m = ReflectionUtils.findMethod(PhaseConfigurationTestListener.class, "defaultPhase", String.class);
|
||||
assertPhase(m, TransactionPhase.AFTER_COMMIT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void phaseSet() {
|
||||
Method m = ReflectionUtils.findMethod(PhaseConfigurationTestListener.class, "phaseSet", String.class);
|
||||
assertPhase(m, TransactionPhase.AFTER_ROLLBACK);
|
||||
}
|
||||
|
||||
private void assertPhase(Method method, TransactionPhase expected) {
|
||||
assertNotNull("Method must not be null", method);
|
||||
TransactionalEventListener annotation = ApplicationListenerMethodTransactionalAdapter.findAnnotation(method);
|
||||
assertEquals("Wrong phase for '" + method + "'", expected, annotation.phase());
|
||||
}
|
||||
|
||||
|
||||
static class PhaseConfigurationTestListener {
|
||||
|
||||
public void noAnnotation(String data) {
|
||||
}
|
||||
|
||||
@TransactionalEventListener
|
||||
public void defaultPhase(String data) {
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
|
||||
public void phaseSet(String data) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,496 @@
|
|||
/*
|
||||
* 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.transaction.event;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.tests.transaction.CallCountingTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.springframework.transaction.event.TransactionPhase.*;
|
||||
|
||||
/**
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public class TransactionalEventListenerTests {
|
||||
|
||||
@Rule
|
||||
public final ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
private ConfigurableApplicationContext context;
|
||||
|
||||
private EventCollector eventCollector;
|
||||
|
||||
private TransactionTemplate transactionTemplate = new TransactionTemplate(new CallCountingTransactionManager());
|
||||
|
||||
@After
|
||||
public void closeContext() {
|
||||
if (this.context != null) {
|
||||
this.context.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void immediately() {
|
||||
load(ImmediateTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertEvents(EventCollector.IMMEDIATELY, "test");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.IMMEDIATELY, "test");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void immediatelyImpactsCurrentTransaction() {
|
||||
load(ImmediateTestListener.class, BeforeCommitTestListener.class);
|
||||
try {
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("FAIL");
|
||||
fail("Should have thrown an exception at this point");
|
||||
return null;
|
||||
});
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertTrue(e.getMessage().contains("Test exception"));
|
||||
assertTrue(e.getMessage().contains(EventCollector.IMMEDIATELY));
|
||||
}
|
||||
getEventCollector().assertEvents(EventCollector.IMMEDIATELY, "FAIL");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterCompletionCommit() {
|
||||
load(AfterCompletionTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.AFTER_COMPLETION, "test");
|
||||
getEventCollector().assertTotalEventsCount(1); // After rollback not invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterCompletionRollback() {
|
||||
load(AfterCompletionTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
status.setRollbackOnly();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.AFTER_COMPLETION, "test");
|
||||
getEventCollector().assertTotalEventsCount(1); // After rollback not invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterCommit() {
|
||||
load(AfterCompletionExplicitTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.AFTER_COMMIT, "test");
|
||||
getEventCollector().assertTotalEventsCount(1); // After rollback not invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
public void afterRollback() {
|
||||
load(AfterCompletionExplicitTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
status.setRollbackOnly();
|
||||
return null;
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.AFTER_ROLLBACK, "test");
|
||||
getEventCollector().assertTotalEventsCount(1); // After commit not invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeCommit() {
|
||||
load(BeforeCommitTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
TransactionSynchronizationManager.registerSynchronization(new EventTransactionSynchronization(10) {
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
getEventCollector().assertNoEventReceived(); // Not seen yet
|
||||
}
|
||||
});
|
||||
TransactionSynchronizationManager.registerSynchronization(new EventTransactionSynchronization(20) {
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
getEventCollector().assertEvents(EventCollector.BEFORE_COMMIT, "test");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
}
|
||||
});
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.BEFORE_COMMIT, "test");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beforeCommitWithException() { // Validates the custom synchronization is invoked
|
||||
load(BeforeCommitTestListener.class);
|
||||
try {
|
||||
this.transactionTemplate.execute(status -> {
|
||||
TransactionSynchronizationManager.registerSynchronization(new EventTransactionSynchronization(10) {
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
throw new IllegalStateException("test");
|
||||
}
|
||||
});
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
fail("Should have thrown an exception");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
// Test exception - ignore
|
||||
}
|
||||
getEventCollector().assertNoEventReceived(); // Before commit not invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
public void regularTransaction() {
|
||||
load(ImmediateTestListener.class, BeforeCommitTestListener.class, AfterCompletionExplicitTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
TransactionSynchronizationManager.registerSynchronization(new EventTransactionSynchronization(10) {
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
getEventCollector().assertTotalEventsCount(1); // Immediate event
|
||||
getEventCollector().assertEvents(EventCollector.IMMEDIATELY, "test");
|
||||
}
|
||||
});
|
||||
TransactionSynchronizationManager.registerSynchronization(new EventTransactionSynchronization(20) {
|
||||
@Override
|
||||
public void beforeCommit(boolean readOnly) {
|
||||
getEventCollector().assertEvents(EventCollector.BEFORE_COMMIT, "test");
|
||||
getEventCollector().assertTotalEventsCount(2);
|
||||
}
|
||||
});
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.AFTER_COMMIT, "test");
|
||||
getEventCollector().assertTotalEventsCount(3); // Immediate, before commit, after commit
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noTransaction() {
|
||||
load(BeforeCommitTestListener.class, AfterCompletionTestListener.class,
|
||||
AfterCompletionExplicitTestListener.class);
|
||||
this.context.publishEvent("test");
|
||||
getEventCollector().assertTotalEventsCount(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noTransactionWithFallbackExecution() {
|
||||
load(FallbackExecutionTestListener.class);
|
||||
this.context.publishEvent("test");
|
||||
this.eventCollector.assertEvents(EventCollector.BEFORE_COMMIT, "test");
|
||||
this.eventCollector.assertEvents(EventCollector.AFTER_COMMIT, "test");
|
||||
this.eventCollector.assertEvents(EventCollector.AFTER_ROLLBACK, "test");
|
||||
this.eventCollector.assertEvents(EventCollector.AFTER_COMPLETION, "test");
|
||||
getEventCollector().assertTotalEventsCount(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conditionFoundOnTransactionalEventListener() {
|
||||
load(ImmediateTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("SKIP");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertNoEventReceived();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("not an event listener if not tagged")
|
||||
public void afterCommitMetaAnnotation() {
|
||||
load(AfterCommitMetaAnnotationTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("test");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertEvents(EventCollector.AFTER_COMMIT, "test");
|
||||
getEventCollector().assertTotalEventsCount(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("not an event listener if not tagged + condition found on wrong annotation")
|
||||
public void conditionFoundOnMetaAnnotation() {
|
||||
load(AfterCommitMetaAnnotationTestListener.class);
|
||||
this.transactionTemplate.execute(status -> {
|
||||
getContext().publishEvent("SKIP");
|
||||
getEventCollector().assertNoEventReceived();
|
||||
return null;
|
||||
|
||||
});
|
||||
getEventCollector().assertNoEventReceived();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class BasicConfiguration {
|
||||
|
||||
@Bean // set automatically with tx management
|
||||
public TransactionalEventListenerFactory transactionalEventListenerFactory() {
|
||||
return new TransactionalEventListenerFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EventCollector eventCollector() {
|
||||
return new EventCollector();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected EventCollector getEventCollector() {
|
||||
return eventCollector;
|
||||
}
|
||||
|
||||
protected ConfigurableApplicationContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
private void load(Class<?>... classes) {
|
||||
List<Class<?>> allClasses = new ArrayList<>();
|
||||
allClasses.add(BasicConfiguration.class);
|
||||
allClasses.addAll(Arrays.asList(classes));
|
||||
doLoad(allClasses.toArray(new Class<?>[allClasses.size()]));
|
||||
}
|
||||
|
||||
private void doLoad(Class<?>... classes) {
|
||||
this.context = new AnnotationConfigApplicationContext(classes);
|
||||
this.eventCollector = this.context.getBean(EventCollector.class);
|
||||
}
|
||||
|
||||
static class EventCollector {
|
||||
|
||||
public static final String IMMEDIATELY = "IMMEDIATELY";
|
||||
|
||||
public static final String BEFORE_COMMIT = "BEFORE_COMMIT";
|
||||
|
||||
public static final String AFTER_COMPLETION = "AFTER_COMPLETION";
|
||||
|
||||
public static final String AFTER_COMMIT = "AFTER_COMMIT";
|
||||
|
||||
public static final String AFTER_ROLLBACK = "AFTER_ROLLBACK";
|
||||
|
||||
public static final String[] ALL_PHASES = {IMMEDIATELY, BEFORE_COMMIT, AFTER_COMMIT, AFTER_ROLLBACK};
|
||||
|
||||
|
||||
private final MultiValueMap<String, Object> events = new LinkedMultiValueMap<>();
|
||||
|
||||
public void addEvent(String phase, Object event) {
|
||||
this.events.add(phase, event);
|
||||
}
|
||||
|
||||
public List<Object> getEvents(String phase) {
|
||||
return this.events.getOrDefault(phase, Collections.emptyList());
|
||||
}
|
||||
|
||||
public void assertNoEventReceived(String... phases) {
|
||||
if (phases.length == 0) { // All values if none set
|
||||
phases = ALL_PHASES;
|
||||
}
|
||||
for (String phase : phases) {
|
||||
List<Object> eventsForPhase = getEvents(phase);
|
||||
assertEquals("Expected no event for phase '" + phase + "' " +
|
||||
"but got " + eventsForPhase, 0, eventsForPhase.size());
|
||||
}
|
||||
}
|
||||
|
||||
public void assertEvents(String phase, Object... expected) {
|
||||
List<Object> actual = getEvents(phase);
|
||||
assertEquals("wrong number of events for phase '" + phase + "'", expected.length, actual.size());
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertEquals("Wrong event for phase '" + phase + "' at index " + i, expected[i], actual.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public void assertTotalEventsCount(int number) {
|
||||
int size = 0;
|
||||
for (Map.Entry<String, List<Object>> entry : this.events.entrySet()) {
|
||||
size += entry.getValue().size();
|
||||
}
|
||||
assertEquals("Wrong number of total events (" + this.events.size() + ") " +
|
||||
"registered phase(s)", number, size);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static abstract class BaseTransactionalTestListener {
|
||||
|
||||
static final String FAIL_MSG = "FAIL";
|
||||
|
||||
@Autowired
|
||||
private EventCollector eventCollector;
|
||||
|
||||
public void handleEvent(String phase, String data) {
|
||||
this.eventCollector.addEvent(phase, data);
|
||||
if (FAIL_MSG.equals(data)) {
|
||||
throw new IllegalStateException("Test exception on phase '" + phase + "'");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
static class ImmediateTestListener extends BaseTransactionalTestListener {
|
||||
|
||||
@EventListener(condition = "!'SKIP'.equals(#data)")
|
||||
public void handleImmediately(String data) {
|
||||
handleEvent(EventCollector.IMMEDIATELY, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
static class AfterCompletionTestListener extends BaseTransactionalTestListener {
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_COMPLETION)
|
||||
public void handleAfterCompletion(String data) {
|
||||
handleEvent(EventCollector.AFTER_COMPLETION, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
static class AfterCompletionExplicitTestListener extends BaseTransactionalTestListener {
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_COMMIT)
|
||||
public void handleAfterCommit(String data) {
|
||||
handleEvent(EventCollector.AFTER_COMMIT, data);
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_ROLLBACK)
|
||||
public void handleAfterRollback(String data) {
|
||||
handleEvent(EventCollector.AFTER_ROLLBACK, data);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
static class BeforeCommitTestListener extends BaseTransactionalTestListener {
|
||||
|
||||
@TransactionalEventListener(phase = BEFORE_COMMIT)
|
||||
@Order(15)
|
||||
public void handleBeforeCommit(String data) {
|
||||
handleEvent(EventCollector.BEFORE_COMMIT, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Component
|
||||
static class FallbackExecutionTestListener extends BaseTransactionalTestListener {
|
||||
|
||||
@TransactionalEventListener(phase = BEFORE_COMMIT, fallbackExecution = true)
|
||||
public void handleBeforeCommit(String data) {
|
||||
handleEvent(EventCollector.BEFORE_COMMIT, data);
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_COMMIT, fallbackExecution = true)
|
||||
public void handleAfterCommit(String data) {
|
||||
handleEvent(EventCollector.AFTER_COMMIT, data);
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_ROLLBACK, fallbackExecution = true)
|
||||
public void handleAfterRollback(String data) {
|
||||
handleEvent(EventCollector.AFTER_ROLLBACK, data);
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_COMPLETION, fallbackExecution = true)
|
||||
public void handleAfterCompletion(String data) {
|
||||
handleEvent(EventCollector.AFTER_COMPLETION, data);
|
||||
}
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = AFTER_COMMIT, condition = "!'SKIP'.equals(#p0)")
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@interface AfterCommitEventListener {
|
||||
}
|
||||
|
||||
@Component
|
||||
static class AfterCommitMetaAnnotationTestListener extends BaseTransactionalTestListener {
|
||||
|
||||
@AfterCommitEventListener
|
||||
public void handleAfterCommit(String data) {
|
||||
handleEvent(EventCollector.AFTER_COMMIT, data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static class EventTransactionSynchronization extends TransactionSynchronizationAdapter {
|
||||
private final int order;
|
||||
|
||||
EventTransactionSynchronization(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in New Issue