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

@@ -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() {