Skip to content

Commit 461d99a

Browse files
committed
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
1 parent 33d5b01 commit 461d99a

File tree

2 files changed

+91
-17
lines changed

2 files changed

+91
-17
lines changed

spring-test/src/main/java/org/springframework/test/context/ContextLoaderUtils.java

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import static org.springframework.beans.BeanUtils.*;
2020
import static org.springframework.core.annotation.AnnotationUtils.*;
2121

22+
import java.lang.annotation.Annotation;
23+
import java.lang.reflect.Constructor;
2224
import java.util.ArrayList;
2325
import java.util.Arrays;
2426
import java.util.HashSet;
@@ -27,10 +29,10 @@
2729

2830
import org.apache.commons.logging.Log;
2931
import org.apache.commons.logging.LogFactory;
32+
3033
import org.springframework.context.ApplicationContextInitializer;
3134
import org.springframework.context.ConfigurableApplicationContext;
32-
import org.springframework.test.context.web.WebAppConfiguration;
33-
import org.springframework.test.context.web.WebMergedContextConfiguration;
35+
import org.springframework.core.annotation.AnnotationUtils;
3436
import org.springframework.util.Assert;
3537
import org.springframework.util.ClassUtils;
3638
import org.springframework.util.ObjectUtils;
@@ -57,6 +59,9 @@ abstract class ContextLoaderUtils {
5759
private static final String DEFAULT_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.DelegatingSmartContextLoader";
5860
private static final String DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME = "org.springframework.test.context.support.WebDelegatingSmartContextLoader";
5961

62+
private static final String WEB_APP_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebAppConfiguration";
63+
private static final String WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME = "org.springframework.test.context.web.WebMergedContextConfiguration";
64+
6065

6166
private ContextLoaderUtils() {
6267
/* no-op */
@@ -69,7 +74,8 @@ private ContextLoaderUtils() {
6974
*
7075
* <p>If the supplied <code>defaultContextLoaderClassName</code> is
7176
* {@code null} or <em>empty</em>, depending on the absence or presence
72-
* of @{@link WebAppConfiguration} either {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
77+
* of {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
78+
* either {@value #DEFAULT_CONTEXT_LOADER_CLASS_NAME}
7379
* or {@value #DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME} will be used as the
7480
* default context loader class name. For details on the class resolution
7581
* process, see {@link #resolveContextLoaderClass()}.
@@ -91,7 +97,9 @@ static ContextLoader resolveContextLoader(Class<?> testClass,
9197
Assert.notEmpty(configAttributesList, "ContextConfigurationAttributes list must not be empty");
9298

9399
if (!StringUtils.hasText(defaultContextLoaderClassName)) {
94-
defaultContextLoaderClassName = testClass.isAnnotationPresent(WebAppConfiguration.class) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
100+
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
101+
defaultContextLoaderClassName = webAppConfigClass != null
102+
&& testClass.isAnnotationPresent(webAppConfigClass) ? DEFAULT_WEB_CONTEXT_LOADER_CLASS_NAME
95103
: DEFAULT_CONTEXT_LOADER_CLASS_NAME;
96104
}
97105

@@ -385,16 +393,82 @@ static MergedContextConfiguration buildMergedContextConfiguration(Class<?> testC
385393
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses = resolveInitializerClasses(configAttributesList);
386394
String[] activeProfiles = resolveActiveProfiles(testClass);
387395

388-
if (testClass.isAnnotationPresent(WebAppConfiguration.class)) {
389-
WebAppConfiguration webAppConfig = testClass.getAnnotation(WebAppConfiguration.class);
390-
String resourceBasePath = webAppConfig.value();
391-
return new WebMergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
392-
resourceBasePath, contextLoader);
396+
MergedContextConfiguration mergedConfig = buildWebMergedContextConfiguration(testClass, locations, classes,
397+
initializerClasses, activeProfiles, contextLoader);
398+
399+
if (mergedConfig == null) {
400+
mergedConfig = new MergedContextConfiguration(testClass, locations, classes, initializerClasses,
401+
activeProfiles, contextLoader);
402+
}
403+
404+
return mergedConfig;
405+
}
406+
407+
/**
408+
* Load the {@link org.springframework.test.context.web.WebAppConfiguration @WebAppConfiguration}
409+
* class using reflection in order to avoid package cycles.
410+
*
411+
* @return the {@code @WebAppConfiguration} class or <code>null</code> if it
412+
* cannot be loaded
413+
*/
414+
@SuppressWarnings("unchecked")
415+
private static Class<? extends Annotation> loadWebAppConfigurationClass() {
416+
Class<? extends Annotation> webAppConfigClass = null;
417+
try {
418+
webAppConfigClass = (Class<? extends Annotation>) ClassUtils.forName(WEB_APP_CONFIGURATION_CLASS_NAME,
419+
ContextLoaderUtils.class.getClassLoader());
420+
}
421+
catch (Throwable t) {
422+
if (logger.isDebugEnabled()) {
423+
logger.debug("Could not load @WebAppConfiguration class [" + WEB_APP_CONFIGURATION_CLASS_NAME + "].", t);
424+
}
425+
}
426+
return webAppConfigClass;
427+
}
428+
429+
/**
430+
* Attempt to build a {@link org.springframework.test.context.web.WebMergedContextConfiguration
431+
* WebMergedContextConfiguration} from the supplied arguments using reflection
432+
* in order to avoid package cycles.
433+
*
434+
* @return the {@code WebMergedContextConfiguration} or <code>null</code> if
435+
* it could not be built
436+
*/
437+
@SuppressWarnings("unchecked")
438+
private static MergedContextConfiguration buildWebMergedContextConfiguration(
439+
Class<?> testClass,
440+
String[] locations,
441+
Class<?>[] classes,
442+
Set<Class<? extends ApplicationContextInitializer<? extends ConfigurableApplicationContext>>> initializerClasses,
443+
String[] activeProfiles, ContextLoader contextLoader) {
444+
445+
Class<? extends Annotation> webAppConfigClass = loadWebAppConfigurationClass();
446+
447+
if (webAppConfigClass != null && testClass.isAnnotationPresent(webAppConfigClass)) {
448+
Annotation annotation = testClass.getAnnotation(webAppConfigClass);
449+
String resourceBasePath = (String) AnnotationUtils.getValue(annotation);
450+
451+
try {
452+
Class<? extends MergedContextConfiguration> webMergedConfigClass = (Class<? extends MergedContextConfiguration>) ClassUtils.forName(
453+
WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME, ContextLoaderUtils.class.getClassLoader());
454+
455+
Constructor<? extends MergedContextConfiguration> constructor = ClassUtils.getConstructorIfAvailable(
456+
webMergedConfigClass, Class.class, String[].class, Class[].class, Set.class, String[].class,
457+
String.class, ContextLoader.class);
458+
459+
if (constructor != null) {
460+
return instantiateClass(constructor, testClass, locations, classes, initializerClasses,
461+
activeProfiles, resourceBasePath, contextLoader);
462+
}
463+
}
464+
catch (Throwable t) {
465+
if (logger.isDebugEnabled()) {
466+
logger.debug("Could not instantiate [" + WEB_MERGED_CONTEXT_CONFIGURATION_CLASS_NAME + "].", t);
467+
}
468+
}
393469
}
394470

395-
// else
396-
return new MergedContextConfiguration(testClass, locations, classes, initializerClasses, activeProfiles,
397-
contextLoader);
471+
return null;
398472
}
399473

400474
}

spring-test/src/main/java/org/springframework/test/context/web/ServletTestExecutionListener.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class ServletTestExecutionListener implements TestExecutionListener {
4848
* The default implementation is <em>empty</em>. Can be overridden by
4949
* subclasses as necessary.
5050
*
51-
* @see org.springframework.test.context.TestExecutionListener#beforeTestClass(TestContext)
51+
* @see TestExecutionListener#beforeTestClass(TestContext)
5252
*/
5353
public void beforeTestClass(TestContext testContext) throws Exception {
5454
/* no-op */
@@ -57,7 +57,7 @@ public void beforeTestClass(TestContext testContext) throws Exception {
5757
/**
5858
* TODO [SPR-9864] Document overridden prepareTestInstance().
5959
*
60-
* @see org.springframework.test.context.TestExecutionListener#prepareTestInstance(TestContext)
60+
* @see TestExecutionListener#prepareTestInstance(TestContext)
6161
*/
6262
public void prepareTestInstance(TestContext testContext) throws Exception {
6363
setUpRequestContextIfNecessary(testContext);
@@ -66,7 +66,7 @@ public void prepareTestInstance(TestContext testContext) throws Exception {
6666
/**
6767
* TODO [SPR-9864] Document overridden beforeTestMethod().
6868
*
69-
* @see org.springframework.test.context.TestExecutionListener#beforeTestMethod(TestContext)
69+
* @see TestExecutionListener#beforeTestMethod(TestContext)
7070
*/
7171
public void beforeTestMethod(TestContext testContext) throws Exception {
7272
setUpRequestContextIfNecessary(testContext);
@@ -75,7 +75,7 @@ public void beforeTestMethod(TestContext testContext) throws Exception {
7575
/**
7676
* TODO [SPR-9864] Document overridden afterTestMethod().
7777
*
78-
* @see org.springframework.test.context.TestExecutionListener#afterTestMethod(TestContext)
78+
* @see TestExecutionListener#afterTestMethod(TestContext)
7979
*/
8080
public void afterTestMethod(TestContext testContext) throws Exception {
8181
if (logger.isDebugEnabled()) {
@@ -88,7 +88,7 @@ public void afterTestMethod(TestContext testContext) throws Exception {
8888
* The default implementation is <em>empty</em>. Can be overridden by
8989
* subclasses as necessary.
9090
*
91-
* @see org.springframework.test.context.TestExecutionListener#afterTestClass(TestContext)
91+
* @see TestExecutionListener#afterTestClass(TestContext)
9292
*/
9393
public void afterTestClass(TestContext testContext) throws Exception {
9494
/* no-op */

0 commit comments

Comments
 (0)