diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/AotContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/aot/AotContextLoader.java
index 932b1a2f2b..6d9a53e1bb 100644
--- a/spring-test/src/main/java/org/springframework/test/context/aot/AotContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/AotContextLoader.java
@@ -28,6 +28,10 @@ import org.springframework.test.context.SmartContextLoader;
* {@linkplain #loadContextForAotRuntime AOT execution} for an integration test
* managed by the Spring TestContext Framework.
*
+ *
{@code AotContextLoader} is an extension of the {@link SmartContextLoader}
+ * SPI that allows a context loader to optionally provide ahead-of-time (AOT)
+ * support.
+ *
*
As of Spring Framework 6.0, AOT infrastructure requires that an {@code AotContextLoader}
* create a {@link org.springframework.context.support.GenericApplicationContext
* GenericApplicationContext} for both build-time processing and run-time execution.
@@ -47,7 +51,7 @@ public interface AotContextLoader extends SmartContextLoader {
* refresh} the {@code ApplicationContext} or
* {@linkplain org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
* register a JVM shutdown hook} for it. Otherwise, this method should implement
- * behavior identical to {@link #loadContext(MergedContextConfiguration)}.
+ * behavior identical to {@code loadContext(MergedContextConfiguration)}.
* @param mergedConfig the merged context configuration to use to load the
* application context
* @return a new {@code GenericApplicationContext}
diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java
index a6e1943193..e003123aa4 100644
--- a/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java
+++ b/spring-test/src/main/java/org/springframework/test/context/aot/TestContextAotGenerator.java
@@ -122,7 +122,7 @@ public class TestContextAotGenerator {
generationContext.writeGeneratedContent();
}
catch (Exception ex) {
- logger.warn(LogMessage.format("Failed to generate AOT artifacts for test classes [%s]",
+ logger.warn(LogMessage.format("Failed to generate AOT artifacts for test classes %s",
testClasses.stream().map(Class::getName).toList()), ex);
}
});
diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java
index 24ea45a1f9..bcfea5500f 100644
--- a/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java
+++ b/spring-test/src/main/java/org/springframework/test/context/cache/DefaultCacheAwareContextLoaderDelegate.java
@@ -105,18 +105,19 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
context = loadContextInternal(mergedContextConfiguration);
}
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Storing ApplicationContext [%s] in cache under key [%s]",
+ logger.debug("Storing ApplicationContext [%s] in cache under key %s".formatted(
System.identityHashCode(context), mergedContextConfiguration));
}
this.contextCache.put(mergedContextConfiguration, context);
}
catch (Exception ex) {
- throw new IllegalStateException("Failed to load ApplicationContext", ex);
+ throw new IllegalStateException(
+ "Failed to load ApplicationContext for " + mergedContextConfiguration, ex);
}
}
else {
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Retrieved ApplicationContext [%s] from cache with key [%s]",
+ logger.debug("Retrieved ApplicationContext [%s] from cache with key %s".formatted(
System.identityHashCode(context), mergedContextConfiguration));
}
}
@@ -150,17 +151,16 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
protected ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration)
throws Exception {
- ContextLoader contextLoader = mergedContextConfiguration.getContextLoader();
- Assert.notNull(contextLoader, "Cannot load an ApplicationContext with a NULL 'contextLoader'. " +
- "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
-
+ ContextLoader contextLoader = getContextLoader(mergedContextConfiguration);
if (contextLoader instanceof SmartContextLoader smartContextLoader) {
return smartContextLoader.loadContext(mergedContextConfiguration);
}
else {
String[] locations = mergedContextConfiguration.getLocations();
- Assert.notNull(locations, "Cannot load an ApplicationContext with a NULL 'locations' array. " +
- "Consider annotating your test class with @ContextConfiguration or @ContextHierarchy.");
+ Assert.notNull(locations, """
+ Cannot load an ApplicationContext with a NULL 'locations' array. \
+ Consider annotating test class [%s] with @ContextConfiguration or \
+ @ContextHierarchy.""".formatted(mergedContextConfiguration.getTestClass().getName()));
return contextLoader.loadContext(locations);
}
}
@@ -172,8 +172,8 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
Assert.state(contextInitializer != null,
() -> "Failed to load AOT ApplicationContextInitializer for test class [%s]"
.formatted(testClass.getName()));
+ ContextLoader contextLoader = getContextLoader(mergedConfig);
logger.info(LogMessage.format("Loading ApplicationContext in AOT mode for %s", mergedConfig));
- ContextLoader contextLoader = mergedConfig.getContextLoader();
if (!((contextLoader instanceof AotContextLoader aotContextLoader) &&
(aotContextLoader.loadContextForAotRuntime(mergedConfig, contextInitializer)
instanceof GenericApplicationContext gac))) {
@@ -187,6 +187,15 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
return gac;
}
+ private ContextLoader getContextLoader(MergedContextConfiguration mergedConfig) {
+ ContextLoader contextLoader = mergedConfig.getContextLoader();
+ Assert.notNull(contextLoader, """
+ Cannot load an ApplicationContext with a NULL 'contextLoader'. \
+ Consider annotating test class [%s] with @ContextConfiguration or \
+ @ContextHierarchy.""".formatted(mergedConfig.getTestClass().getName()));
+ return contextLoader;
+ }
+
/**
* Determine if we are running in AOT mode for the supplied test class.
*/
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
index 90b750ceb2..a9a9904578 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractDelegatingSmartContextLoader.java
@@ -60,12 +60,17 @@ import org.springframework.util.Assert;
* (e.g., XML configuration files and Groovy scripts) or annotated classes,
* but not both simultaneously.
*
- *
As of Spring Framework 3.2, a test class may optionally declare neither path-based
* resource locations nor annotated classes and instead declare only {@linkplain
* ContextConfiguration#initializers application context initializers}. In such
* cases, an attempt will still be made to detect defaults, but their absence will
* not result in an exception.
*
+ *
As of Spring Framework 6.0, this class implements {@link AotContextLoader}.
+ * Consequently, the candidate {@link #getXmlLoader()} and
+ * {@link #getAnnotationConfigLoader()} must also implement {@code AotContextLoader}
+ * in order to provide AOT support.
+ *
* @author Sam Brannen
* @author Phillip Webb
* @since 3.2
@@ -195,144 +200,103 @@ public abstract class AbstractDelegatingSmartContextLoader implements AotContext
*
* @param mergedConfig the merged context configuration to use to load the application context
* @return a new application context
- * @throws IllegalArgumentException if the supplied merged configuration is {@code null}
* @throws IllegalStateException if neither candidate loader is capable of loading an
* {@code ApplicationContext} from the supplied merged context configuration
*/
@Override
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
- return loadContext(mergedConfig, true);
+ SmartContextLoader loader = getContextLoader(mergedConfig);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Delegating to %s to load context for %s".formatted(name(loader), mergedConfig));
+ }
+ return loader.loadContext(mergedConfig);
}
/**
* Delegates to an appropriate candidate {@code SmartContextLoader} to load
* an {@link ApplicationContext} for AOT processing.
*
Delegation is based on explicit knowledge of the implementations of the
- * default loaders for {@linkplain #getXmlLoader() XML configuration files and
- * Groovy scripts} and {@linkplain #getAnnotationConfigLoader() annotated classes}.
- * Specifically, the delegation algorithm is as follows:
- *
initializer) throws Exception {
- return getAotContextLoader(mergedConfig, "load").loadContextForAotRuntime(mergedConfig, initializer);
+ AotContextLoader loader = getAotContextLoader(mergedConfig);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Delegating to %s to load context for AOT processing for %s"
+ .formatted(name(loader), mergedConfig));
+ }
+ return loader.loadContextForAotProcessing(mergedConfig);
}
/**
* Delegates to an appropriate candidate {@code SmartContextLoader} to load
- * an {@link ApplicationContext}.
+ * an {@link ApplicationContext} for AOT run-time execution.
+ * Delegation is based on explicit knowledge of the implementations of the
+ * default loaders. See {@link #loadContext(MergedContextConfiguration)} for
+ * details.
* @param mergedConfig the merged context configuration to use to load the application context
- * @param refresh whether to refresh the {@code ApplicationContext} and register
- * a JVM shutdown hook for it
+ * @param initializer the {@code ApplicationContextInitializer} that should
+ * be applied to the context in order to recreate bean definitions
* @return a new application context
- * @throws IllegalArgumentException if the supplied merged configuration is {@code null}
* @throws IllegalStateException if neither candidate loader is capable of loading an
* {@code ApplicationContext} from the supplied merged context configuration
+ * @since 6.0
+ * @see AotContextLoader#loadContextForAotRuntime(MergedContextConfiguration, ApplicationContextInitializer)
*/
- private ApplicationContext loadContext(MergedContextConfiguration mergedConfig, boolean refresh) throws Exception {
- assertPreconditions(mergedConfig, "load");
+ @Override
+ public final ApplicationContext loadContextForAotRuntime(MergedContextConfiguration mergedConfig,
+ ApplicationContextInitializer initializer) throws Exception {
+
+ AotContextLoader loader = getAotContextLoader(mergedConfig);
+ if (logger.isDebugEnabled()) {
+ logger.debug("Delegating to %s to load context for AOT execution for %s"
+ .formatted(name(loader), mergedConfig));
+ }
+ return loader.loadContextForAotRuntime(mergedConfig, initializer);
+ }
+
+ private SmartContextLoader getContextLoader(MergedContextConfiguration mergedConfig) {
+ Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null");
+ Assert.state(!(mergedConfig.hasLocations() && mergedConfig.hasClasses()), () -> """
+ Neither %s nor %s is able to load an ApplicationContext for %s: \
+ declare either 'locations' or 'classes' but not both.""".formatted(
+ name(getXmlLoader()), name(getAnnotationConfigLoader()), mergedConfig));
SmartContextLoader[] candidates = {getXmlLoader(), getAnnotationConfigLoader()};
for (SmartContextLoader loader : candidates) {
// Determine if each loader can load a context from the mergedConfig. If it
// can, let it; otherwise, keep iterating.
if (supports(loader, mergedConfig)) {
- return delegateLoading(loader, mergedConfig, refresh);
+ return loader;
}
}
// If neither of the candidates supports the mergedConfig based on resources but
- // ACIs or customizers were declared, then delegate to the annotation config
- // loader.
- if (!mergedConfig.getContextInitializerClasses().isEmpty() || !mergedConfig.getContextCustomizers().isEmpty()) {
- return delegateLoading(getAnnotationConfigLoader(), mergedConfig, refresh);
+ // ACIs or customizers were declared, then delegate to the annotation config loader.
+ if (hasInitializersOrCustomizers(mergedConfig)) {
+ return getAnnotationConfigLoader();
}
// else...
- throw new IllegalStateException(String.format(
- "Neither %s nor %s is able to load an ApplicationContext for %s.",
- name(getXmlLoader()), name(getAnnotationConfigLoader()), mergedConfig));
- }
-
- private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) {
- if (logger.isDebugEnabled()) {
- logger.debug(String.format("Delegating to %s to process context configuration %s.",
- name(loader), configAttributes));
- }
- loader.processContextConfiguration(configAttributes);
- }
-
- private static ApplicationContext delegateLoading(
- SmartContextLoader loader, MergedContextConfiguration mergedConfig, boolean refresh)
- throws Exception {
-
- if (logger.isDebugEnabled()) {
- logger.debug(String.format("Delegating to %s to load context for %s.", name(loader), mergedConfig));
- }
- if (refresh) {
- return loader.loadContext(mergedConfig);
- }
- else {
- if (loader instanceof AotContextLoader aotContextLoader) {
- return aotContextLoader.loadContextForAotProcessing(mergedConfig);
- }
- throw new IllegalStateException(
- "%s must implement AotContextLoader to load the ApplicationContext for %s."
- .formatted(name(loader), mergedConfig));
- }
- }
-
- private AotContextLoader getAotContextLoader(MergedContextConfiguration mergedConfig, String action) {
- assertPreconditions(mergedConfig, action);
-
- SmartContextLoader[] candidates = {getXmlLoader(), getAnnotationConfigLoader()};
- for (SmartContextLoader loader : candidates) {
- // Determine if each loader can load a context from the mergedConfig. If it
- // can, let it; otherwise, keep iterating.
- if (loader instanceof AotContextLoader aotContextLoader && supports(loader, mergedConfig)) {
- return aotContextLoader;
- }
- }
-
- // If neither of the candidates supports the mergedConfig based on resources but
- // ACIs or customizers were declared, then delegate to the annotation config
- // loader.
- if (getAnnotationConfigLoader() instanceof AotContextLoader aotContextLoader &&
- (!mergedConfig.getContextInitializerClasses().isEmpty() ||
- !mergedConfig.getContextCustomizers().isEmpty())) {
- return aotContextLoader;
- }
-
throw new IllegalStateException(
- "Neither %s nor %s is able to %s the ApplicationContext for %s.".formatted(
- name(getXmlLoader()), name(getAnnotationConfigLoader()), action, mergedConfig));
+ "Neither %s nor %s is able to load an ApplicationContext for %s.".formatted(
+ name(getXmlLoader()), name(getAnnotationConfigLoader()), mergedConfig));
}
- private void assertPreconditions(MergedContextConfiguration mergedConfig, String action) {
- Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null");
- Assert.state(!(mergedConfig.hasLocations() && mergedConfig.hasClasses()), () -> """
- Neither %s nor %s is able to %s an ApplicationContext for %s: \
- declare either 'locations' or 'classes' but not both.""".formatted(
- name(getXmlLoader()), name(getAnnotationConfigLoader()), action, mergedConfig));
+ private AotContextLoader getAotContextLoader(MergedContextConfiguration mergedConfig) {
+ SmartContextLoader loader = getContextLoader(mergedConfig);
+ if (!(loader instanceof AotContextLoader aotContextLoader)) {
+ throw new IllegalStateException("%s must be an AotContextLoader".formatted(name(loader)));
+ }
+ return aotContextLoader;
}
private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) {
@@ -344,6 +308,19 @@ public abstract class AbstractDelegatingSmartContextLoader implements AotContext
}
}
+
+ private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Delegating to %s to process context configuration %s.".formatted(name(loader), configAttributes));
+ }
+ loader.processContextConfiguration(configAttributes);
+ }
+
+ private static boolean hasInitializersOrCustomizers(MergedContextConfiguration mergedConfig) {
+ return !(mergedConfig.getContextInitializerClasses().isEmpty() &&
+ mergedConfig.getContextCustomizers().isEmpty());
+ }
+
private static String name(SmartContextLoader loader) {
return loader.getClass().getSimpleName();
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
index d518dff262..68f38553e7 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AbstractGenericContextLoader.java
@@ -16,6 +16,8 @@
package org.springframework.test.context.support;
+import java.util.Arrays;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -28,7 +30,7 @@ import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.aot.AotContextLoader;
-import org.springframework.util.StringUtils;
+import org.springframework.util.Assert;
/**
* Abstract, generic extension of {@link AbstractContextLoader} that loads a
@@ -104,12 +106,12 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
*/
@Override
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
- return loadContext(mergedConfig, true);
+ return loadContext(mergedConfig, false);
}
/**
- * Load a {@link GenericApplicationContext} for the supplied
- * {@link MergedContextConfiguration}.
+ * Load a {@link GenericApplicationContext} for AOT build-time processing based
+ * on the supplied {@link MergedContextConfiguration}.
* In contrast to {@link #loadContext(MergedContextConfiguration)}, this
* method does not
* {@linkplain org.springframework.context.ConfigurableApplicationContext#refresh()
@@ -120,23 +122,38 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @param mergedConfig the merged context configuration to use to load the
* application context
* @return a new application context
+ * @throws Exception if context loading failed
* @since 6.0
- * @see org.springframework.test.context.SmartContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
+ * @see AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
*/
@Override
- public final ApplicationContext loadContextForAotProcessing(
- MergedContextConfiguration mergedConfig) throws Exception {
-
- return loadContext(mergedConfig, false);
+ public final GenericApplicationContext loadContextForAotProcessing(MergedContextConfiguration mergedConfig)
+ throws Exception {
+ return loadContext(mergedConfig, true);
}
+ /**
+ * Load a {@link GenericApplicationContext} for AOT run-time execution based on
+ * the supplied {@link MergedContextConfiguration} and
+ * {@link ApplicationContextInitializer}.
+ * @param mergedConfig the merged context configuration to use to load the
+ * application context
+ * @param initializer the {@code ApplicationContextInitializer} that should
+ * be applied to the context in order to recreate bean definitions
+ * @return a new application context
+ * @throws Exception if context loading failed
+ * @since 6.0
+ * @see AotContextLoader#loadContextForAotRuntime(MergedContextConfiguration, ApplicationContextInitializer)
+ */
@Override
- public final ApplicationContext loadContextForAotRuntime(MergedContextConfiguration mergedConfig,
+ public final GenericApplicationContext loadContextForAotRuntime(MergedContextConfiguration mergedConfig,
ApplicationContextInitializer initializer) throws Exception {
+ Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null");
+ Assert.notNull(initializer, "ApplicationContextInitializer must not be null");
+
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Loading ApplicationContext for AOT runtime for merged context configuration [%s].",
- mergedConfig));
+ logger.debug("Loading ApplicationContext for AOT runtime for merged context configuration " + mergedConfig);
}
validateMergedContextConfiguration(mergedConfig);
@@ -156,16 +173,17 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* {@link MergedContextConfiguration}.
* @param mergedConfig the merged context configuration to use to load the
* application context
- * @param refresh whether to refresh the {@code ApplicationContext} and register
- * a JVM shutdown hook for it
+ * @param forAotProcessing {@code true} if the context is being loaded for
+ * AOT processing, meaning not to refresh the {@code ApplicationContext} or
+ * register a JVM shutdown hook for it
* @return a new application context
*/
private final GenericApplicationContext loadContext(
- MergedContextConfiguration mergedConfig, boolean refresh) throws Exception {
+ MergedContextConfiguration mergedConfig, boolean forAotProcessing) throws Exception {
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Loading ApplicationContext for merged context configuration [%s].",
- mergedConfig));
+ logger.debug("Loading ApplicationContext %sfor merged context configuration %s"
+ .formatted((forAotProcessing ? "for AOT processing " : ""), mergedConfig));
}
validateMergedContextConfiguration(mergedConfig);
@@ -184,7 +202,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
customizeContext(context);
customizeContext(context, mergedConfig);
- if (refresh) {
+ if (!forAotProcessing) {
context.refresh();
context.registerShutdownHook();
}
@@ -236,14 +254,13 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @see org.springframework.test.context.ContextLoader#loadContext
* @see GenericApplicationContext
* @see #loadContext(MergedContextConfiguration)
- * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContextForAotProcessing(MergedContextConfiguration)}
+ * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration)}
*/
@Deprecated
@Override
public final ConfigurableApplicationContext loadContext(String... locations) throws Exception {
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Loading ApplicationContext for locations [%s].",
- StringUtils.arrayToCommaDelimitedString(locations)));
+ logger.debug("Loading ApplicationContext for locations " + Arrays.toString(locations));
}
GenericApplicationContext context = createContext();
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
index 7b3f043253..cc2b7286eb 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/AnnotationConfigContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2022 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,6 +16,8 @@
package org.springframework.test.context.support;
+import java.util.Arrays;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -24,7 +26,6 @@ import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.MergedContextConfiguration;
-import org.springframework.util.ObjectUtils;
/**
* Concrete implementation of {@link AbstractGenericContextLoader} that loads
@@ -155,10 +156,11 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
@Override
protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
if (mergedConfig.hasLocations()) {
- String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'locations' " +
- "(or 'value') attribute %s, but %s does not support resource locations.",
- mergedConfig.getTestClass().getName(), ObjectUtils.nullSafeToString(mergedConfig.getLocations()),
- getClass().getSimpleName());
+ String msg = """
+ Test class [%s] has been configured with @ContextConfiguration's 'locations' \
+ (or 'value') attribute %s, but %s does not support resource locations."""
+ .formatted(mergedConfig.getTestClass().getName(),
+ Arrays.toString(mergedConfig.getLocations()), getClass().getSimpleName());
logger.error(msg);
throw new IllegalStateException(msg);
}
@@ -181,7 +183,7 @@ public class AnnotationConfigContextLoader extends AbstractGenericContextLoader
protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
Class>[] componentClasses = mergedConfig.getClasses();
if (logger.isDebugEnabled()) {
- logger.debug("Registering component classes: " + ObjectUtils.nullSafeToString(componentClasses));
+ logger.debug("Registering component classes: " + Arrays.toString(componentClasses));
}
new AnnotatedBeanDefinitionReader(context).register(componentClasses);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContext.java b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContext.java
index 2c2f1318a7..86ec4441be 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContext.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/DefaultTestContext.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2022 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.
@@ -123,13 +123,14 @@ public class DefaultTestContext implements TestContext {
public ApplicationContext getApplicationContext() {
ApplicationContext context = this.cacheAwareContextLoaderDelegate.loadContext(this.mergedContextConfiguration);
if (context instanceof ConfigurableApplicationContext cac) {
- Assert.state(cac.isActive(), () ->
- "The ApplicationContext loaded for [" + this.mergedContextConfiguration +
- "] is not active. This may be due to one of the following reasons: " +
- "1) the context was closed programmatically by user code; " +
- "2) the context was closed during parallel test execution either " +
- "according to @DirtiesContext semantics or due to automatic eviction " +
- "from the ContextCache due to a maximum cache size policy.");
+ Assert.state(cac.isActive(), () -> """
+ The ApplicationContext loaded for %s is not active. \
+ This may be due to one of the following reasons: \
+ 1) the context was closed programmatically by user code; \
+ 2) the context was closed during parallel test execution either \
+ according to @DirtiesContext semantics or due to automatic eviction \
+ from the ContextCache due to a maximum cache size policy."""
+ .formatted(this.mergedContextConfiguration));
}
return context;
}
@@ -206,7 +207,7 @@ public class DefaultTestContext implements TestContext {
Assert.notNull(computeFunction, "Compute function must not be null");
Object value = this.attributes.computeIfAbsent(name, computeFunction);
Assert.state(value != null,
- () -> String.format("Compute function must not return null for attribute named '%s'", name));
+ () -> "Compute function must not return null for attribute named '%s'".formatted(name));
return (T) value;
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java
index c07ca5f8ee..0970994216 100644
--- a/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/support/GenericXmlContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2014 the original author or authors.
+ * Copyright 2002-2022 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,11 +16,12 @@
package org.springframework.test.context.support;
+import java.util.Arrays;
+
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.test.context.MergedContextConfiguration;
-import org.springframework.util.ObjectUtils;
/**
* Concrete implementation of {@link AbstractGenericContextLoader} that reads
@@ -64,10 +65,10 @@ public class GenericXmlContextLoader extends AbstractGenericContextLoader {
@Override
protected void validateMergedContextConfiguration(MergedContextConfiguration mergedConfig) {
if (mergedConfig.hasClasses()) {
- String msg = String.format(
- "Test class [%s] has been configured with @ContextConfiguration's 'classes' attribute %s, "
- + "but %s does not support annotated classes.", mergedConfig.getTestClass().getName(),
- ObjectUtils.nullSafeToString(mergedConfig.getClasses()), getClass().getSimpleName());
+ String msg = """
+ Test class [%s] has been configured with @ContextConfiguration's 'classes' attribute %s, \
+ but %s does not support annotated classes.""".formatted(mergedConfig.getTestClass().getName(),
+ Arrays.toString(mergedConfig.getClasses()), getClass().getSimpleName());
logger.error(msg);
throw new IllegalStateException(msg);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
index 0adad9d74b..f87e5423b3 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/AbstractGenericWebContextLoader.java
@@ -64,57 +64,6 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
protected static final Log logger = LogFactory.getLog(AbstractGenericWebContextLoader.class);
- // AotContextLoader
-
- /**
- * Load a {@link GenericWebApplicationContext} for the supplied
- * {@link MergedContextConfiguration}.
- * In contrast to {@link #loadContext(MergedContextConfiguration)}, this
- * method does not
- * {@linkplain org.springframework.context.ConfigurableApplicationContext#refresh()
- * refresh} the {@code ApplicationContext} or
- * {@linkplain org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
- * register a JVM shutdown hook} for it. Otherwise, this method implements
- * behavior identical to {@link #loadContext(MergedContextConfiguration)}.
- * @param mergedConfig the merged context configuration to use to load the
- * application context
- * @return a new web application context
- * @since 6.0
- * @see org.springframework.test.context.aot.AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
- * @see GenericWebApplicationContext
- */
- @Override
- public final ApplicationContext loadContextForAotProcessing(
- MergedContextConfiguration mergedConfig) throws Exception {
-
- return loadContext(mergedConfig, false);
- }
-
- @Override
- public final ApplicationContext loadContextForAotRuntime(MergedContextConfiguration mergedConfig,
- ApplicationContextInitializer initializer) throws Exception {
-
- if (!(mergedConfig instanceof WebMergedContextConfiguration webMergedConfig)) {
- throw new IllegalArgumentException("""
- Cannot load WebApplicationContext from non-web merged context configuration %s. \
- Consider annotating your test class with @WebAppConfiguration."""
- .formatted(mergedConfig));
- }
-
- validateMergedContextConfiguration(webMergedConfig);
-
- GenericWebApplicationContext context = createContext();
- configureWebResources(context, webMergedConfig);
- prepareContext(context, webMergedConfig);
- initializer.initialize(context);
- customizeContext(context, webMergedConfig);
- context.refresh();
- return context;
- }
-
-
- // SmartContextLoader
-
/**
* Load a {@link GenericWebApplicationContext} for the supplied
* {@link MergedContextConfiguration}.
@@ -149,26 +98,86 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
* application context
* @return a new web application context
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
- * @see GenericWebApplicationContext
*/
@Override
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
+ return loadContext(mergedConfig, false);
+ }
+
+ /**
+ * Load a {@link GenericWebApplicationContext} for AOT build-time processing based
+ * on the supplied {@link MergedContextConfiguration}.
+ * In contrast to {@link #loadContext(MergedContextConfiguration)}, this
+ * method does not
+ * {@linkplain org.springframework.context.ConfigurableApplicationContext#refresh()
+ * refresh} the {@code ApplicationContext} or
+ * {@linkplain org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
+ * register a JVM shutdown hook} for it. Otherwise, this method implements
+ * behavior identical to {@link #loadContext(MergedContextConfiguration)}.
+ * @param mergedConfig the merged context configuration to use to load the
+ * application context
+ * @return a new web application context
+ * @throws Exception if context loading failed
+ * @since 6.0
+ * @see AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
+ */
+ @Override
+ public final GenericWebApplicationContext loadContextForAotProcessing(MergedContextConfiguration mergedConfig)
+ throws Exception {
return loadContext(mergedConfig, true);
}
+ /**
+ * Load a {@link GenericWebApplicationContext} for AOT run-time execution based on
+ * the supplied {@link MergedContextConfiguration} and
+ * {@link ApplicationContextInitializer}.
+ * @param mergedConfig the merged context configuration to use to load the
+ * application context
+ * @param initializer the {@code ApplicationContextInitializer} that should
+ * be applied to the context in order to recreate bean definitions
+ * @return a new web application context
+ * @throws Exception if context loading failed
+ * @since 6.0
+ * @see AotContextLoader#loadContextForAotRuntime(MergedContextConfiguration, ApplicationContextInitializer)
+ */
+ @Override
+ public final GenericWebApplicationContext loadContextForAotRuntime(MergedContextConfiguration mergedConfig,
+ ApplicationContextInitializer initializer) throws Exception {
+
+ Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null");
+ Assert.notNull(initializer, "ApplicationContextInitializer must not be null");
+ if (!(mergedConfig instanceof WebMergedContextConfiguration webMergedConfig)) {
+ throw new IllegalArgumentException("""
+ Cannot load WebApplicationContext from non-web merged context configuration %s. \
+ Consider annotating your test class with @WebAppConfiguration."""
+ .formatted(mergedConfig));
+ }
+
+ validateMergedContextConfiguration(webMergedConfig);
+
+ GenericWebApplicationContext context = createContext();
+ configureWebResources(context, webMergedConfig);
+ prepareContext(context, webMergedConfig);
+ initializer.initialize(context);
+ customizeContext(context, webMergedConfig);
+ context.refresh();
+ return context;
+ }
+
/**
* Load a {@link GenericWebApplicationContext} for the supplied
* {@link MergedContextConfiguration}.
* @param mergedConfig the merged context configuration to use to load the
* application context
- * @param refresh whether to refresh the {@code ApplicationContext} and register
- * a JVM shutdown hook for it
+ * @param forAotProcessing {@code true} if the context is being loaded for
+ * AOT processing, meaning not to refresh the {@code ApplicationContext} or
+ * register a JVM shutdown hook for it
* @return a new web application context
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
- * @see org.springframework.test.context.SmartContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
+ * @see org.springframework.test.context.aot.AotContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
*/
private final GenericWebApplicationContext loadContext(
- MergedContextConfiguration mergedConfig, boolean refresh) throws Exception {
+ MergedContextConfiguration mergedConfig, boolean forAotProcessing) throws Exception {
if (!(mergedConfig instanceof WebMergedContextConfiguration webMergedConfig)) {
throw new IllegalArgumentException("""
@@ -178,8 +187,8 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
}
if (logger.isDebugEnabled()) {
- logger.debug(String.format("Loading WebApplicationContext for merged context configuration %s.",
- webMergedConfig));
+ logger.debug("Loading WebApplicationContext %sfor merged context configuration %s"
+ .formatted((forAotProcessing ? "for AOT processing " : ""), mergedConfig));
}
validateMergedContextConfiguration(webMergedConfig);
@@ -197,7 +206,7 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
AnnotationConfigUtils.registerAnnotationConfigProcessors(context);
customizeContext(context, webMergedConfig);
- if (refresh) {
+ if (!forAotProcessing) {
context.refresh();
context.registerShutdownHook();
}
@@ -343,8 +352,6 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
}
- // ContextLoader
-
/**
* {@code AbstractGenericWebContextLoader} should be used as a
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader},
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java
index 877d52109a..bd01d72792 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/AnnotationConfigWebContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2022 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,6 +16,8 @@
package org.springframework.test.context.web;
+import java.util.Arrays;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -23,7 +25,6 @@ import org.springframework.context.annotation.AnnotatedBeanDefinitionReader;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.AnnotationConfigContextLoaderUtils;
-import org.springframework.util.ObjectUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
@@ -159,7 +160,7 @@ public class AnnotationConfigWebContextLoader extends AbstractGenericWebContextL
Class>[] annotatedClasses = webMergedConfig.getClasses();
if (logger.isDebugEnabled()) {
- logger.debug("Registering annotated classes: " + ObjectUtils.nullSafeToString(annotatedClasses));
+ logger.debug("Registering annotated classes: " + Arrays.toString(annotatedClasses));
}
new AnnotatedBeanDefinitionReader(context).register(annotatedClasses);
}
@@ -173,10 +174,11 @@ public class AnnotationConfigWebContextLoader extends AbstractGenericWebContextL
@Override
protected void validateMergedContextConfiguration(WebMergedContextConfiguration webMergedConfig) {
if (webMergedConfig.hasLocations()) {
- String msg = String.format("Test class [%s] has been configured with @ContextConfiguration's 'locations' " +
- "(or 'value') attribute %s, but %s does not support resource locations.",
- webMergedConfig.getTestClass().getName(),
- ObjectUtils.nullSafeToString(webMergedConfig.getLocations()), getClass().getSimpleName());
+ String msg = """
+ Test class [%s] has been configured with @ContextConfiguration's 'locations' \
+ (or 'value') attribute %s, but %s does not support resource locations."""
+ .formatted(webMergedConfig.getTestClass().getName(),
+ Arrays.toString(webMergedConfig.getLocations()), getClass().getSimpleName());
logger.error(msg);
throw new IllegalStateException(msg);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java b/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java
index bf7e38063b..65cebd2ac6 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/GenericXmlWebContextLoader.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2015 the original author or authors.
+ * Copyright 2002-2022 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,9 +16,10 @@
package org.springframework.test.context.web;
+import java.util.Arrays;
+
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.test.context.MergedContextConfiguration;
-import org.springframework.util.ObjectUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
/**
@@ -65,10 +66,11 @@ public class GenericXmlWebContextLoader extends AbstractGenericWebContextLoader
@Override
protected void validateMergedContextConfiguration(WebMergedContextConfiguration webMergedConfig) {
if (webMergedConfig.hasClasses()) {
- String msg = String.format(
- "Test class [%s] has been configured with @ContextConfiguration's 'classes' attribute %s, "
- + "but %s does not support annotated classes.", webMergedConfig.getTestClass().getName(),
- ObjectUtils.nullSafeToString(webMergedConfig.getClasses()), getClass().getSimpleName());
+ String msg = """
+ Test class [%s] has been configured with @ContextConfiguration's 'classes' \
+ attribute %s, but %s does not support annotated classes."""
+ .formatted(webMergedConfig.getTestClass().getName(),
+ Arrays.toString(webMergedConfig.getClasses()), getClass().getSimpleName());
logger.error(msg);
throw new IllegalStateException(msg);
}
diff --git a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
index a1663ef8f5..be21a0747f 100644
--- a/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
+++ b/spring-test/src/main/java/org/springframework/test/context/web/WebMergedContextConfiguration.java
@@ -29,16 +29,18 @@ import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
- * {@code WebMergedContextConfiguration} encapsulates the merged
- * context configuration declared on a test class and all of its superclasses
- * via {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
- * {@link WebAppConfiguration @WebAppConfiguration}, and
- * {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles}.
+ * {@code WebMergedContextConfiguration} encapsulates the merged context
+ * configuration declared on a test class and all of its superclasses and
+ * enclosing classes via
+ * {@link org.springframework.test.context.ContextConfiguration @ContextConfiguration},
+ * {@link WebAppConfiguration @WebAppConfiguration},
+ * {@link org.springframework.test.context.ActiveProfiles @ActiveProfiles}, and
+ * {@link org.springframework.test.context.TestPropertySource @TestPropertySource}.
*
* {@code WebMergedContextConfiguration} extends the contract of
- * {@link MergedContextConfiguration} by adding support for the {@link
+ * {@link MergedContextConfiguration} by adding support for the {@linkplain
* #getResourceBasePath() resource base path} configured via {@code @WebAppConfiguration}.
- * This allows the {@link org.springframework.test.context.TestContext TestContext}
+ * This allows the {@link org.springframework.test.context.cache.ContextCache ContextCache}
* to properly cache the corresponding {@link
* org.springframework.web.context.WebApplicationContext WebApplicationContext}
* that was loaded using properties of this {@code WebMergedContextConfiguration}.
@@ -69,7 +71,7 @@ public class WebMergedContextConfiguration extends MergedContextConfiguration {
*/
public WebMergedContextConfiguration(MergedContextConfiguration mergedConfig, String resourceBasePath) {
super(mergedConfig);
- this.resourceBasePath = !StringUtils.hasText(resourceBasePath) ? "" : resourceBasePath;
+ this.resourceBasePath = (StringUtils.hasText(resourceBasePath) ? resourceBasePath : "");
}
/**