From 461d99af29e8c358ff80ff9ff58a8ea63fe7b670 Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 28 Oct 2012 00:13:01 +0200 Subject: [PATCH] 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 --- .../test/context/ContextLoaderUtils.java | 98 ++++++++++++++++--- .../web/ServletTestExecutionListener.java | 10 +- 2 files changed, 91 insertions(+), 17 deletions(-) diff --git a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java index 5e2e235707..425878a543 100644 --- a/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java @@ -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 { * *

If the supplied defaultContextLoaderClassName is * {@code null} or empty, 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 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>> 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 null if it + * cannot be loaded + */ + @SuppressWarnings("unchecked") + private static Class loadWebAppConfigurationClass() { + Class webAppConfigClass = null; + try { + webAppConfigClass = (Class) 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 null if + * it could not be built + */ + @SuppressWarnings("unchecked") + private static MergedContextConfiguration buildWebMergedContextConfiguration( + Class testClass, + String[] locations, + Class[] classes, + Set>> initializerClasses, + String[] activeProfiles, ContextLoader contextLoader) { + + Class webAppConfigClass = loadWebAppConfigurationClass(); + + if (webAppConfigClass != null && testClass.isAnnotationPresent(webAppConfigClass)) { + Annotation annotation = testClass.getAnnotation(webAppConfigClass); + String resourceBasePath = (String) AnnotationUtils.getValue(annotation); + + try { + Class webMergedConfigClass = (Class) ClassUtils.forName( + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader()); + + Constructor 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; } } diff --git a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java index 969234061e..1e999d7e13 100644 --- a/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java @@ -48,7 +48,7 @@ public class ServletTestExecutionListener implements TestExecutionListener { * The default implementation is empty. 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 empty. 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 */