Provide support for context hierarchies in the TCF

Prior to this commit the Spring TestContext Framework supported creating
only flat, non-hierarchical contexts. There was no easy way to create
contexts with parent-child relationships.

This commit addresses this issue by introducing a new @ContextHierarchy
annotation that can be used in conjunction with @ContextConfiguration
for declaring hierarchies of application contexts, either within a
single test class or within a test class hierarchy. In addition,
@DirtiesContext now supports a new 'hierarchyMode' attribute for
controlling context cache clearing for context hierarchies.

- Introduced a new @ContextHierarchy annotation.
- Introduced 'name' attribute in @ContextConfiguration.
- Introduced 'name' property in ContextConfigurationAttributes.
- TestContext is now aware of @ContextHierarchy in addition to
  @ContextConfiguration.
- Introduced findAnnotationDeclaringClassForTypes() in AnnotationUtils.
- Introduced resolveContextHierarchyAttributes() in ContextLoaderUtils.
- Introduced buildContextHierarchyMap() in ContextLoaderUtils.
- @ContextConfiguration and @ContextHierarchy may not be used as
  top-level, class-level annotations simultaneously.
- Introduced reference to the parent configuration in
  MergedContextConfiguration and WebMergedContextConfiguration.
- Introduced overloaded buildMergedContextConfiguration() methods in
  ContextLoaderUtils in order to handle context hierarchies separately
  from conventional, non-hierarchical contexts.
- Introduced hashCode() and equals() in ContextConfigurationAttributes.
- ContextLoaderUtils ensures uniqueness of @ContextConfiguration
  elements within a single @ContextHierarchy declaration.
- Introduced CacheAwareContextLoaderDelegate that can be used for
  loading contexts with transparent support for interacting with the
  context cache -- for example, for retrieving the parent application
  context in a context hierarchy.
- TestContext now delegates to CacheAwareContextLoaderDelegate for
  loading contexts.
- Introduced getParentApplicationContext() in MergedContextConfiguration
- The loadContext(MergedContextConfiguration) methods in
  AbstractGenericContextLoader and AbstractGenericWebContextLoader now
  set the parent context as appropriate.
- Introduced 'hierarchyMode' attribute in @DirtiesContext with a
  corresponding HierarchyMode enum that defines EXHAUSTIVE and
  CURRENT_LEVEL cache removal modes.
- ContextCache now internally tracks the relationships between contexts
  that make up a context hierarchy. Furthermore, when a context is
  removed, if it is part of a context hierarchy all corresponding
  contexts will be removed from the cache according to the supplied
  HierarchyMode.
- AbstractGenericWebContextLoader will set a loaded context as the
  ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE in the MockServletContext when
  context hierarchies are used if the context has no parent or if the
  context has a parent that is not a WAC.
- Where appropriate, updated Javadoc to refer to the
  ServletTestExecutionListener, which was introduced in 3.2.0.
- Updated Javadoc to avoid and/or suppress warnings in spring-test.
- Suppressed remaining warnings in code in spring-test.

Issue: SPR-5613, SPR-9863
This commit is contained in:
Sam Brannen
2013-01-11 01:02:08 +01:00
parent 7bc5353e07
commit 98074e7762
55 changed files with 3919 additions and 551 deletions

View File

@@ -20,6 +20,7 @@ import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
@@ -240,14 +241,58 @@ public abstract class AnnotationUtils {
* if not found
* @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations()
* @see #findAnnotationDeclaringClassForTypes(List, Class)
* @see #isAnnotationDeclaredLocally(Class, Class)
*/
public static Class<?> findAnnotationDeclaringClass(Class<? extends Annotation> annotationType, Class<?> clazz) {
Assert.notNull(annotationType, "Annotation type must not be null");
if (clazz == null || clazz.equals(Object.class)) {
return null;
}
return (isAnnotationDeclaredLocally(annotationType, clazz)) ? clazz :
findAnnotationDeclaringClass(annotationType, clazz.getSuperclass());
return (isAnnotationDeclaredLocally(annotationType, clazz)) ? clazz : findAnnotationDeclaringClass(
annotationType, clazz.getSuperclass());
}
/**
* Find the first {@link Class} in the inheritance hierarchy of the specified
* {@code clazz} (including the specified {@code clazz} itself) which declares
* at least one of the specified {@code annotationTypes}, or {@code null} if
* none of the specified annotation types could be found.
* <p>If the supplied {@code clazz} is {@code null}, {@code null} will be
* returned.
* <p>If the supplied {@code clazz} is an interface, only the interface itself
* will be checked; the inheritance hierarchy for interfaces will not be traversed.
* <p>The standard {@link Class} API does not provide a mechanism for determining
* which class in an inheritance hierarchy actually declares one of several
* candidate {@linkplain Annotation annotations}, so we need to handle this
* explicitly.
* @param annotationTypes the list of Class objects corresponding to the
* annotation types
* @param clazz the Class object corresponding to the class on which to check
* for the annotations, or {@code null}
* @return the first {@link Class} in the inheritance hierarchy of the specified
* {@code clazz} which declares an annotation of at least one of the specified
* {@code annotationTypes}, or {@code null} if not found
* @see Class#isAnnotationPresent(Class)
* @see Class#getDeclaredAnnotations()
* @see #findAnnotationDeclaringClass(Class, Class)
* @see #isAnnotationDeclaredLocally(Class, Class)
* @since 3.2.2
*/
public static Class<?> findAnnotationDeclaringClassForTypes(List<Class<? extends Annotation>> annotationTypes,
Class<?> clazz) {
Assert.notEmpty(annotationTypes, "The list of annotation types must not be empty");
if (clazz == null || clazz.equals(Object.class)) {
return null;
}
for (Class<? extends Annotation> annotationType : annotationTypes) {
if (isAnnotationDeclaredLocally(annotationType, clazz)) {
return clazz;
}
}
return findAnnotationDeclaringClassForTypes(annotationTypes, clazz.getSuperclass());
}
/**
@@ -348,8 +393,8 @@ public abstract class AnnotationUtils {
* and corresponding attribute values as values
* @since 3.1.1
*/
public static AnnotationAttributes getAnnotationAttributes(
Annotation annotation, boolean classValuesAsString, boolean nestedAnnotationsAsMap) {
public static AnnotationAttributes getAnnotationAttributes(Annotation annotation, boolean classValuesAsString,
boolean nestedAnnotationsAsMap) {
AnnotationAttributes attrs = new AnnotationAttributes();
Method[] methods = annotation.annotationType().getDeclaredMethods();
@@ -371,15 +416,15 @@ public abstract class AnnotationUtils {
}
}
if (nestedAnnotationsAsMap && value instanceof Annotation) {
attrs.put(method.getName(), getAnnotationAttributes(
(Annotation)value, classValuesAsString, nestedAnnotationsAsMap));
attrs.put(method.getName(),
getAnnotationAttributes((Annotation) value, classValuesAsString, nestedAnnotationsAsMap));
}
else if (nestedAnnotationsAsMap && value instanceof Annotation[]) {
Annotation[] realAnnotations = (Annotation[])value;
Annotation[] realAnnotations = (Annotation[]) value;
AnnotationAttributes[] mappedAnnotations = new AnnotationAttributes[realAnnotations.length];
for (int i = 0; i < realAnnotations.length; i++) {
mappedAnnotations[i] = getAnnotationAttributes(
realAnnotations[i], classValuesAsString, nestedAnnotationsAsMap);
mappedAnnotations[i] = getAnnotationAttributes(realAnnotations[i], classValuesAsString,
nestedAnnotationsAsMap);
}
attrs.put(method.getName(), mappedAnnotations);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2013 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.
@@ -16,21 +16,26 @@
package org.springframework.core.annotation;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
import java.lang.annotation.Annotation;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import static org.junit.Assert.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
/**
* Unit tests for {@link AnnotationUtils}.
*
* @author Rod Johnson
* @author Juergen Hoeller
* @author Sam Brannen
@@ -102,23 +107,91 @@ public class AnnotationUtilsTests {
assertNull(findAnnotationDeclaringClass(Transactional.class, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertEquals(InheritedAnnotationInterface.class, findAnnotationDeclaringClass(Transactional.class,
InheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClass(Transactional.class, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClass(Transactional.class, SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class, findAnnotationDeclaringClass(Transactional.class,
InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class, findAnnotationDeclaringClass(Transactional.class,
SubInheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClass(Transactional.class, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClass(Transactional.class, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClass() should still find it.
assertEquals(NonInheritedAnnotationInterface.class, findAnnotationDeclaringClass(Order.class,
NonInheritedAnnotationInterface.class));
// but findAnnotationDeclaringClass() should still find it on classes.
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClass(Order.class, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClass(Order.class, SubNonInheritedAnnotationInterface.class));
assertEquals(NonInheritedAnnotationClass.class, findAnnotationDeclaringClass(Order.class,
NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class, findAnnotationDeclaringClass(Order.class,
SubNonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClass(Order.class, NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClass(Order.class, SubNonInheritedAnnotationClass.class));
}
@Test
public void findAnnotationDeclaringClassForTypesWithSingleCandidateType() {
// no class-level annotation
List<Class<? extends Annotation>> transactionalCandidateList = Arrays.<Class<? extends Annotation>> asList(Transactional.class);
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(transactionalCandidateList,
SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(transactionalCandidateList, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
List<Class<? extends Annotation>> orderCandidateList = Arrays.<Class<? extends Annotation>> asList(Order.class);
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationInterface.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(orderCandidateList, SubNonInheritedAnnotationClass.class));
}
@Test
public void findAnnotationDeclaringClassForTypesWithMultipleCandidateTypes() {
List<Class<? extends Annotation>> candidates = Arrays.<Class<? extends Annotation>> asList(Transactional.class,
Order.class);
// no class-level annotation
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(candidates, NonAnnotatedClass.class));
// inherited class-level annotation; note: @Transactional is inherited
assertEquals(InheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(candidates, InheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(candidates, SubInheritedAnnotationInterface.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, InheritedAnnotationClass.class));
assertEquals(InheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, SubInheritedAnnotationClass.class));
// non-inherited class-level annotation; note: @Order is not inherited,
// but findAnnotationDeclaringClassForTypes() should still find it on classes.
assertEquals(NonInheritedAnnotationInterface.class,
findAnnotationDeclaringClassForTypes(candidates, NonInheritedAnnotationInterface.class));
assertNull(findAnnotationDeclaringClassForTypes(candidates, SubNonInheritedAnnotationInterface.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, NonInheritedAnnotationClass.class));
assertEquals(NonInheritedAnnotationClass.class,
findAnnotationDeclaringClassForTypes(candidates, SubNonInheritedAnnotationClass.class));
// class hierarchy mixed with @Transactional and @Order declarations
assertEquals(TransactionalClass.class,
findAnnotationDeclaringClassForTypes(candidates, TransactionalClass.class));
assertEquals(TransactionalAndOrderedClass.class,
findAnnotationDeclaringClassForTypes(candidates, TransactionalAndOrderedClass.class));
assertEquals(TransactionalAndOrderedClass.class,
findAnnotationDeclaringClassForTypes(candidates, SubTransactionalAndOrderedClass.class));
}
@Test
@@ -216,18 +289,18 @@ public class AnnotationUtilsTests {
}
@Component(value="meta1")
@Component(value = "meta1")
@Retention(RetentionPolicy.RUNTIME)
@interface Meta1 {
}
@Component(value="meta2")
@Component(value = "meta2")
@Retention(RetentionPolicy.RUNTIME)
@interface Meta2 {
}
@Meta1
@Component(value="local")
@Component(value = "local")
@Meta2
static class HasLocalAndMetaComponentAnnotation {
}
@@ -332,6 +405,16 @@ public class AnnotationUtilsTests {
public static class SubNonInheritedAnnotationClass extends NonInheritedAnnotationClass {
}
@Transactional
public static class TransactionalClass {
}
@Order
public static class TransactionalAndOrderedClass {
}
public static class SubTransactionalAndOrderedClass extends TransactionalAndOrderedClass {
}
public static interface InterfaceWithAnnotatedMethod {
@@ -353,10 +436,12 @@ public class AnnotationUtilsTests {
}
}
public abstract static class AbstractDoesNotImplementInterfaceWithAnnotatedMethod implements InterfaceWithAnnotatedMethod {
public abstract static class AbstractDoesNotImplementInterfaceWithAnnotatedMethod implements
InterfaceWithAnnotatedMethod {
}
public static class SubOfAbstractImplementsInterfaceWithAnnotatedMethod extends AbstractDoesNotImplementInterfaceWithAnnotatedMethod {
public static class SubOfAbstractImplementsInterfaceWithAnnotatedMethod extends
AbstractDoesNotImplementInterfaceWithAnnotatedMethod {
@Override
public void foo() {