Fix package cycles in spring-test

Code introduced in conjunction with SPR-5243 introduced package cycles
between the ~.test.context and ~.test.context.web packages. This was
caused by the fact that ContextLoaderUtils worked directly with the
@WebAppConfiguration and WebMergedContextConfiguration types.

To address this, the following methods have been introduced in
ContextLoaderUtils. These methods use reflection to circumvent hard
dependencies on the @WebAppConfiguration and
WebMergedContextConfiguration types.

 - loadWebAppConfigurationClass()
 - buildWebMergedContextConfiguration()

Issue: SPR-9924
This commit is contained in:
Sam Brannen 2012-10-28 00:13:01 +02:00
parent 33d5b011d3
commit 461d99af29
2 changed files with 91 additions and 17 deletions

View File

@ -19,6 +19,8 @@ package org.springframework.test.context;
import static org.springframework.beans.BeanUtils.*;
import static org.springframework.core.annotation.AnnotationUtils.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@ -27,10 +29,10 @@ import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.context.web.WebMergedContextConfiguration;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
@ -57,6 +59,9 @@ abstract class ContextLoaderUtils {
private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.WebDelegatingSmartContextLoader";
private static final String WEB_APP_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration";
private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebMergedContextConfiguration";
private ContextLoaderUtils() {
/* no-op */
@ -69,7 +74,8 @@ abstract class ContextLoaderUtils {
*
* <p>If the supplied <code>defaultContextLoaderClassName</code> is
* {@code null} or <em>empty</em>, depending on the absence or presence
* of @{@link WebAppConfiguration} either {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
* of {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
* either {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
* or {@value #DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME} will be used as the
* default context loader class name. For details on the class resolution
* process, see {@link #resolveContextLoaderClass()}.
@ -91,7 +97,9 @@ abstract class ContextLoaderUtils {
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
defaultContextLoaderClassName = testClass.isAnnotationPresent(WebAppConfiguration.class) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
defaultContextLoaderClassName = webAppConfigClass != null
&& testClass.isAnnotationPresent(webAppConfigClass) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
: DEFAULT_CONTEXT_LOADER_CLASS_NAME;
}
@ -385,16 +393,82 @@ abstract class ContextLoaderUtils {
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList);
String[] activeProfiles = resolveActiveProfiles(testClass);
if (testClass.isAnnotationPresent(WebAppConfiguration.class)) {
WebAppConfiguration webAppConfig = testClass.getAnnotation(WebAppConfiguration.class);
String resourceBasePath = webAppConfig.value();
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
resourceBasePath, contextLoader);
MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
initializerClasses, activeProfiles, contextLoader);
if (mergedConfig == null) {
mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
activeProfiles, contextLoader);
}
// else
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
contextLoader);
return mergedConfig;
}
/**
* Load the {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
* class using reflection in order to avoid package cycles.
*
* @return the {@code @WebAppConfiguration} class or <code>null</code> if it
* cannot be loaded
*/
@SuppressWarnings("unchecked")
private static Class<? extends Annotation> loadWebAppConfigurationClass() {
Class<? extends Annotation> webAppConfigClass = null;
try {
webAppConfigClass = (Class<? extends Annotation>) ClassUtils.forName(WEB_APP_CONFIGURATION_CLASS_NAME,
ContextLoaderUtils.class.getClassLoader());
}
catch (Throwable t) {
if (logger.isDebugEnabled()) {
logger.debug("Could not load @WebAppConfiguration class [" + WEB_APP_CONFIGURATION_CLASS_NAME + "].", t);
}
}
return webAppConfigClass;
}
/**
* Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration
* WebMergedContextConfiguration} from the supplied arguments using reflection
* in order to avoid package cycles.
*
* @return the {@code WebMergedContextConfiguration} or <code>null</code> if
* it could not be built
*/
@SuppressWarnings("unchecked")
private static MergedContextConfiguration buildWebMergedContextConfiguration(
Class<?> testClass,
String[] locations,
Class<?>[] classes,
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
String[] activeProfiles, ContextLoader contextLoader) {
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
if (webAppConfigClass != null && testClass.isAnnotationPresent(webAppConfigClass)) {
Annotation annotation = testClass.getAnnotation(webAppConfigClass);
String resourceBasePath = (String) AnnotationUtils.getValue(annotation);
try {
Class<? extends MergedContextConfiguration> webMergedConfigClass = (Class<? extends MergedContextConfiguration>) ClassUtils.forName(
WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader());
Constructor<? extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class,
String.class, ContextLoader.class);
if (constructor != null) {
return instantiateClass(constructor, testClass, locations, classes, initializerClasses,
activeProfiles, resourceBasePath, contextLoader);
}
}
catch (Throwable t) {
if (logger.isDebugEnabled()) {
logger.debug("Could not instantiate [" + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME + "].", t);
}
}
}
return null;
}
}

View File

@ -48,7 +48,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
* The default implementation is <em>empty</em>. Can be overridden by
* subclasses as necessary.
*
* @see org.springframework.test.context.TestExecutionListener#beforeTestClass(TestContext)
* @see TestExecutionListener#beforeTestClass(TestContext)
*/
public void beforeTestClass(TestContext testContext) throws Exception {
/* no-op */
@ -57,7 +57,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
/**
* TODO [SPR-9864] Document overridden prepareTestInstance().
*
* @see org.springframework.test.context.TestExecutionListener#prepareTestInstance(TestContext)
* @see TestExecutionListener#prepareTestInstance(TestContext)
*/
public void prepareTestInstance(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
@ -66,7 +66,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
/**
* TODO [SPR-9864] Document overridden beforeTestMethod().
*
* @see org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)
* @see TestExecutionListener#beforeTestMethod(TestContext)
*/
public void beforeTestMethod(TestContext testContext) throws Exception {
setUpRequestContextIfNecessary(testContext);
@ -75,7 +75,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
/**
* TODO [SPR-9864] Document overridden afterTestMethod().
*
* @see org.springframework.test.context.TestExecutionListener#afterTestMethod(TestContext)
* @see TestExecutionListener#afterTestMethod(TestContext)
*/
public void afterTestMethod(TestContext testContext) throws Exception {
if (logger.isDebugEnabled()) {
@ -88,7 +88,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
* The default implementation is <em>empty</em>. Can be overridden by
* subclasses as necessary.
*
* @see org.springframework.test.context.TestExecutionListener#afterTestClass(TestContext)
* @see TestExecutionListener#afterTestClass(TestContext)
*/
public void afterTestClass(TestContext testContext) throws Exception {
/* no-op */