Support @[Before|After]Transaction on default methods
Prior to this commit, @BeforeTransaction and @AfterTransaction could only be declared on methods within test classes. However, JUnit 5 as well as some existing third-party Runner implementations for JUnit 4 already support Java 8 based interface default methods in various scenarios -- for example, @Test, @BeforeEach, etc. This commit brings the Spring TestContext Framework up to date by supporting the declaration of @BeforeTransaction and @AfterTransaction on interface default methods. Issue: SPR-14183
This commit is contained in:
@@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>Test annotation to indicate that the annotated {@code void} method
|
||||
* <p>Test annotation which indicates that the annotated {@code void} method
|
||||
* should be executed <em>after</em> a transaction is ended for a test method
|
||||
* configured to run within a transaction via the {@code @Transactional} annotation.
|
||||
* configured to run within a transaction via Spring's {@code @Transactional}
|
||||
* annotation.
|
||||
*
|
||||
* <p>The {@code @AfterTransaction} methods of superclasses will be executed
|
||||
* after those of the current class.
|
||||
* <p>As of Spring Framework 4.3, {@code @AfterTransaction} may be declared on
|
||||
* Java 8 based interface default methods.
|
||||
*
|
||||
* <p>{@code @AfterTransaction} methods declared in superclasses or as interface
|
||||
* default methods will be executed after those of the current test class.
|
||||
*
|
||||
* <p>As of Spring Framework 4.0, this annotation may be used as a
|
||||
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
|
||||
|
||||
@@ -23,12 +23,16 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* <p>Test annotation to indicate that the annotated {@code void} method
|
||||
* <p>Test annotation which indicates that the annotated {@code void} method
|
||||
* should be executed <em>before</em> a transaction is started for a test method
|
||||
* configured to run within a transaction via the {@code @Transactional} annotation.
|
||||
* configured to run within a transaction via Spring's {@code @Transactional}
|
||||
* annotation.
|
||||
*
|
||||
* <p>The {@code @BeforeTransaction} methods of superclasses will be executed
|
||||
* before those of the current class.
|
||||
* <p>As of Spring Framework 4.3, {@code @BeforeTransaction} may be declared on
|
||||
* Java 8 based interface default methods.
|
||||
*
|
||||
* <p>{@code @BeforeTransaction} methods declared in superclasses or as interface
|
||||
* default methods will be executed before those of the current test class.
|
||||
*
|
||||
* <p>As of Spring Framework 4.0, this annotation may be used as a
|
||||
* <em>meta-annotation</em> to create custom <em>composed annotations</em>.
|
||||
|
||||
@@ -97,9 +97,10 @@ import org.springframework.util.StringUtils;
|
||||
* <p>When executing transactional tests, it is sometimes useful to be able to
|
||||
* execute certain <em>set up</em> or <em>tear down</em> code outside of a
|
||||
* transaction. {@code TransactionalTestExecutionListener} provides such
|
||||
* support for methods annotated with
|
||||
* {@link BeforeTransaction @BeforeTransaction} or
|
||||
* {@link AfterTransaction @AfterTransaction}.
|
||||
* support for methods annotated with {@link BeforeTransaction @BeforeTransaction}
|
||||
* or {@link AfterTransaction @AfterTransaction}. As of Spring Framework 4.3,
|
||||
* {@code @BeforeTransaction} and {@code @AfterTransaction} may also be declared
|
||||
* on Java 8 based interface default methods.
|
||||
*
|
||||
* <h3>Configuring a Transaction Manager</h3>
|
||||
* <p>{@code TransactionalTestExecutionListener} expects a
|
||||
@@ -431,90 +432,23 @@ public class TransactionalTestExecutionListener extends AbstractTestExecutionLis
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all superclasses of the supplied {@link Class class}, including the
|
||||
* class itself. The ordering of the returned list will begin with the
|
||||
* supplied class and continue up the class hierarchy, excluding {@link Object}.
|
||||
* <p>Note: This code has been borrowed from
|
||||
* {@link org.junit.internal.runners.TestClass#getSuperClasses(Class)} and
|
||||
* adapted.
|
||||
* @param clazz the class for which to retrieve the superclasses
|
||||
* @return all superclasses of the supplied class, excluding {@code Object}
|
||||
*/
|
||||
private List<Class<?>> getSuperClasses(Class<?> clazz) {
|
||||
List<Class<?>> results = new ArrayList<Class<?>>();
|
||||
Class<?> current = clazz;
|
||||
while (current != null && Object.class != current) {
|
||||
results.add(current);
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all methods in the supplied {@link Class class} and its superclasses
|
||||
* Get all methods in the supplied {@link Class class} and its superclasses
|
||||
* which are annotated with the supplied {@code annotationType} but
|
||||
* which are not <em>shadowed</em> by methods overridden in subclasses.
|
||||
* <p>Note: This code has been borrowed from
|
||||
* {@link org.junit.internal.runners.TestClass#getAnnotatedMethods(Class)}
|
||||
* and adapted.
|
||||
* <p>Default methods on interfaces are also detected.
|
||||
* @param clazz the class for which to retrieve the annotated methods
|
||||
* @param annotationType the annotation type for which to search
|
||||
* @return all annotated methods in the supplied class and its superclasses
|
||||
* as well as annotated interface default methods
|
||||
*/
|
||||
private List<Method> getAnnotatedMethods(Class<?> clazz, Class<? extends Annotation> annotationType) {
|
||||
List<Method> results = new ArrayList<Method>();
|
||||
for (Class<?> current : getSuperClasses(clazz)) {
|
||||
for (Method method : current.getDeclaredMethods()) {
|
||||
Annotation annotation = AnnotationUtils.getAnnotation(method, annotationType);
|
||||
if (annotation != null && !isShadowed(method, results)) {
|
||||
results.add(method);
|
||||
}
|
||||
List<Method> methods = new ArrayList<Method>(4);
|
||||
for (Method method : ReflectionUtils.getUniqueDeclaredMethods(clazz)) {
|
||||
if (AnnotationUtils.getAnnotation(method, annotationType) != null) {
|
||||
methods.add(method);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@link Method method} is <em>shadowed</em> by
|
||||
* a method in the supplied {@link List list} of previous methods.
|
||||
* <p>Note: This code has been borrowed from
|
||||
* {@link org.junit.internal.runners.TestClass#isShadowed(Method, List)}.
|
||||
* @param method the method to check for shadowing
|
||||
* @param previousMethods the list of methods which have previously been processed
|
||||
* @return {@code true} if the supplied method is shadowed by a
|
||||
* method in the {@code previousMethods} list
|
||||
*/
|
||||
private boolean isShadowed(Method method, List<Method> previousMethods) {
|
||||
for (Method each : previousMethods) {
|
||||
if (isShadowed(method, each)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the supplied {@linkplain Method current method} is
|
||||
* <em>shadowed</em> by a {@linkplain Method previous method}.
|
||||
* <p>Note: This code has been borrowed from
|
||||
* {@link org.junit.internal.runners.TestClass#isShadowed(Method, Method)}.
|
||||
* @param current the current method
|
||||
* @param previous the previous method
|
||||
* @return {@code true} if the previous method shadows the current one
|
||||
*/
|
||||
private boolean isShadowed(Method current, Method previous) {
|
||||
if (!previous.getName().equals(current.getName())) {
|
||||
return false;
|
||||
}
|
||||
if (previous.getParameterTypes().length != current.getParameterTypes().length) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < previous.getParameterTypes().length; i++) {
|
||||
if (!previous.getParameterTypes()[i].equals(current.getParameterTypes()[i])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return methods;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user