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:
@@ -19,6 +19,8 @@ package org.springframework.test.context;
|
|||||||
import static org.springframework.beans.BeanUtils.*;
|
import static org.springframework.beans.BeanUtils.*;
|
||||||
import static org.springframework.core.annotation.AnnotationUtils.*;
|
import static org.springframework.core.annotation.AnnotationUtils.*;
|
||||||
|
|
||||||
|
import java.lang.annotation.Annotation;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
@@ -27,10 +29,10 @@ import java.util.Set;
|
|||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContextInitializer;
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
import org.springframework.context.ConfigurableApplicationContext;
|
import org.springframework.context.ConfigurableApplicationContext;
|
||||||
import org.springframework.test.context.web.WebAppConfiguration;
|
import org.springframework.core.annotation.AnnotationUtils;
|
||||||
import org.springframework.test.context.web.WebMergedContextConfiguration;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.ClassUtils;
|
import org.springframework.util.ClassUtils;
|
||||||
import org.springframework.util.ObjectUtils;
|
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_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 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() {
|
private ContextLoaderUtils() {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
@@ -69,7 +74,8 @@ abstract class ContextLoaderUtils {
|
|||||||
*
|
*
|
||||||
* <p>If the supplied <code>defaultContextLoaderClassName</code> is
|
* <p>If the supplied <code>defaultContextLoaderClassName</code> is
|
||||||
* {@code null} or <em>empty</em>, depending on the absence or presence
|
* {@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
|
* or {@value #DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME} will be used as the
|
||||||
* default context loader class name. For details on the class resolution
|
* default context loader class name. For details on the class resolution
|
||||||
* process, see {@link #resolveContextLoaderClass()}.
|
* process, see {@link #resolveContextLoaderClass()}.
|
||||||
@@ -91,7 +97,9 @@ abstract class ContextLoaderUtils {
|
|||||||
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
|
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
|
||||||
|
|
||||||
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
|
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;
|
: DEFAULT_CONTEXT_LOADER_CLASS_NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,16 +393,82 @@ abstract class ContextLoaderUtils {
|
|||||||
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList);
|
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList);
|
||||||
String[] activeProfiles = resolveActiveProfiles(testClass);
|
String[] activeProfiles = resolveActiveProfiles(testClass);
|
||||||
|
|
||||||
if (testClass.isAnnotationPresent(WebAppConfiguration.class)) {
|
MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
|
||||||
WebAppConfiguration webAppConfig = testClass.getAnnotation(WebAppConfiguration.class);
|
initializerClasses, activeProfiles, contextLoader);
|
||||||
String resourceBasePath = webAppConfig.value();
|
|
||||||
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
|
if (mergedConfig == null) {
|
||||||
resourceBasePath, contextLoader);
|
mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
|
||||||
|
activeProfiles, contextLoader);
|
||||||
}
|
}
|
||||||
|
|
||||||
// else
|
return mergedConfig;
|
||||||
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
|
}
|
||||||
contextLoader);
|
|
||||||
|
/**
|
||||||
|
* 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
|
|||||||
* The default implementation is <em>empty</em>. Can be overridden by
|
* The default implementation is <em>empty</em>. Can be overridden by
|
||||||
* subclasses as necessary.
|
* subclasses as necessary.
|
||||||
*
|
*
|
||||||
* @see org.springframework.test.context.TestExecutionListener#beforeTestClass(TestContext)
|
* @see TestExecutionListener#beforeTestClass(TestContext)
|
||||||
*/
|
*/
|
||||||
public void beforeTestClass(TestContext testContext) throws Exception {
|
public void beforeTestClass(TestContext testContext) throws Exception {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
@@ -57,7 +57,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
|
|||||||
/**
|
/**
|
||||||
* TODO [SPR-9864] Document overridden prepareTestInstance().
|
* 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 {
|
public void prepareTestInstance(TestContext testContext) throws Exception {
|
||||||
setUpRequestContextIfNecessary(testContext);
|
setUpRequestContextIfNecessary(testContext);
|
||||||
@@ -66,7 +66,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
|
|||||||
/**
|
/**
|
||||||
* TODO [SPR-9864] Document overridden beforeTestMethod().
|
* 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 {
|
public void beforeTestMethod(TestContext testContext) throws Exception {
|
||||||
setUpRequestContextIfNecessary(testContext);
|
setUpRequestContextIfNecessary(testContext);
|
||||||
@@ -75,7 +75,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
|
|||||||
/**
|
/**
|
||||||
* TODO [SPR-9864] Document overridden afterTestMethod().
|
* 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 {
|
public void afterTestMethod(TestContext testContext) throws Exception {
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
@@ -88,7 +88,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
|
|||||||
* The default implementation is <em>empty</em>. Can be overridden by
|
* The default implementation is <em>empty</em>. Can be overridden by
|
||||||
* subclasses as necessary.
|
* subclasses as necessary.
|
||||||
*
|
*
|
||||||
* @see org.springframework.test.context.TestExecutionListener#afterTestClass(TestContext)
|
* @see TestExecutionListener#afterTestClass(TestContext)
|
||||||
*/
|
*/
|
||||||
public void afterTestClass(TestContext testContext) throws Exception {
|
public void afterTestClass(TestContext testContext) throws Exception {
|
||||||
/* no-op */
|
/* no-op */
|
||||||
|
|||||||
Reference in New Issue
Block a user