提交 0f6711fe 编写于 作者: S Sam Brannen

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
上级 7ce5ba4a
......@@ -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);
}
}
}
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;
List<Method> methods = new ArrayList<Method>(4);
for (Method method : ReflectionUtils.getUniqueDeclaredMethods(clazz)) {
if (AnnotationUtils.getAnnotation(method, annotationType) != null) {
methods.add(method);
}
}
return true;
return methods;
}
/**
......
......@@ -20,7 +20,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
......@@ -245,13 +244,11 @@ public class TransactionalTestExecutionListenerTests {
assertAfterTestMethod(AfterTransactionDeclaredViaMetaAnnotationTestCase.class);
}
@Ignore("Disabled until @BeforeTransaction is supported on interface default methods")
@Test
public void beforeTestMethodWithBeforeTransactionDeclaredAsInterfaceDefaultMethod() throws Exception {
assertBeforeTestMethod(BeforeTransactionDeclaredAsInterfaceDefaultMethodTestCase.class);
}
@Ignore("Disabled until @AfterTransaction is supported on interface default methods")
@Test
public void afterTestMethodWithAfterTransactionDeclaredAsInterfaceDefaultMethod() throws Exception {
assertAfterTestMethod(AfterTransactionDeclaredAsInterfaceDefaultMethodTestCase.class);
......
......@@ -854,9 +854,12 @@ method, potentially overriding class-level `@Rollback` or `@Commit` semantics.
+
Indicates that the annotated `void` method should be executed __before__ a
transaction is started for test methods configured to run within a transaction via the
`@Transactional` annotation.
Indicates that the annotated `void` method should be executed __before__ a transaction
is started for test methods configured to run within a transaction via Spring's
`@Transactional` annotation. As of Spring Framework 4.3, `@BeforeTransaction` methods
are not required to be `public` and may be declared on Java 8 based interface default
methods.
+
......@@ -873,9 +876,11 @@ transaction is started for test methods configured to run within a transaction v
+
Indicates that the annotated `void` method should be executed __after__ a
transaction has ended for test methods configured to run within a transaction via the
`@Transactional` annotation.
Indicates that the annotated `void` method should be executed __after__ a transaction
is ended for test methods configured to run within a transaction via Spring's
`@Transactional` annotation. As of Spring Framework 4.3, `@AfterTransaction` methods
are not required to be `public` and may be declared on Java 8 based interface default
methods.
+
......@@ -3210,12 +3215,12 @@ javadocs for `TestTransaction` for further details.
Occasionally you need to execute certain code before or after a transactional test method
but outside the transactional context -- for example, to verify the initial database state
prior to execution of your test or to verify expected transactional commit behavior after
test execution (if the test was configured not to roll back the transaction).
test execution (if the test was configured to commit the transaction).
`TransactionalTestExecutionListener` supports the `@BeforeTransaction` and
`@AfterTransaction` annotations exactly for such scenarios. Simply annotate any `void`
method in your test class with one of these annotations, and the
`TransactionalTestExecutionListener` ensures that your __before transaction method__ or
__after transaction method__ is executed at the appropriate time.
method in a test class or any `void` default method in a test interface with one of these
annotations, and the `TransactionalTestExecutionListener` ensures that your __before
transaction method__ or __after transaction method__ is executed at the appropriate time.
[TIP]
====
......
......@@ -694,8 +694,9 @@ Spring 4.3 also improves the caching abstraction as follows:
* New `SpringRunner` _alias_ for the `SpringJUnit4ClassRunner`.
* An empty declaration of `@ContextConfiguration` can now be completely omitted if default
XML files, Groovy scripts, or `@Configuration` classes are detected.
* `@Transactional` test methods are no longer required to be `public` (in TestNG and JUnit 5).
* `@BeforeTransaction` and `@AfterTransaction` methods are no longer required to be `public`.
* `@Transactional` test methods are no longer required to be `public` (e.g., in TestNG and JUnit 5).
* `@BeforeTransaction` and `@AfterTransaction` methods are no longer required to be `public`
and may now be declared on Java 8 based interface default methods.
* The `ApplicationContext` cache in the _Spring TestContext Framework_ is now bounded with a
default maximum size of 32 and a _least recently used_ eviction policy. The maximum size
can be configured by setting a JVM system property or Spring property called
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册