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