Revise configurable refresh support in SmartContextLoader SPI

This commit revises 903e9f2a02 based on feedback from the Spring Boot
team.

- The original loadContext(MergedContextConfiguration) method is no
  longer deprecated.

- loadContext(MergedContextConfiguration, boolean) has been replaced by
  loadContextForAotProcessing(MergedContextConfiguration) which is
  implemented as a `default` method that throws an
  UnsupportedOperationException.

- Affected code has been refactored to adjust to these changes.

See gh-28906
This commit is contained in:
Sam Brannen
2022-08-03 16:54:32 +03:00
parent 3a890033b7
commit e3f4d810f6
11 changed files with 178 additions and 135 deletions

View File

@@ -27,8 +27,8 @@ import org.springframework.lang.Nullable;
* introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process * introduced in Spring 2.5: a {@code SmartContextLoader} can choose to process
* resource locations, component classes, or a combination of both. Furthermore, a * resource locations, component classes, or a combination of both. Furthermore, a
* {@code SmartContextLoader} can configure the context that it * {@code SmartContextLoader} can configure the context that it
* {@linkplain #loadContext(MergedContextConfiguration, boolean) loads} based on * {@linkplain #loadContext(MergedContextConfiguration) loads} based on any
* any properties available in the provided {@link MergedContextConfiguration}. * properties available in the provided {@link MergedContextConfiguration}.
* For example, active bean definition profiles can be configured for the context * For example, active bean definition profiles can be configured for the context
* based on {@link MergedContextConfiguration#getActiveProfiles()}. * based on {@link MergedContextConfiguration#getActiveProfiles()}.
* *
@@ -38,14 +38,14 @@ import org.springframework.lang.Nullable;
* <p>Clients of a {@code SmartContextLoader} should call * <p>Clients of a {@code SmartContextLoader} should call
* {@link #processContextConfiguration(ContextConfigurationAttributes) * {@link #processContextConfiguration(ContextConfigurationAttributes)
* processContextConfiguration()} prior to calling * processContextConfiguration()} prior to calling
* {@link #loadContext(MergedContextConfiguration, boolean) loadContext()}. This * {@link #loadContext(MergedContextConfiguration) loadContext()}. This gives a
* gives a {@code SmartContextLoader} the opportunity to provide custom support * {@code SmartContextLoader} the opportunity to provide custom support for
* for modifying resource locations or detecting default resource locations or * modifying resource locations or detecting default resource locations or
* default configuration classes. The results of * default configuration classes. The results of
* {@link #processContextConfiguration(ContextConfigurationAttributes) * {@link #processContextConfiguration(ContextConfigurationAttributes)
* processContextConfiguration()} should be merged for all classes in the * processContextConfiguration()} should be merged for all classes in the
* hierarchy of the root test class and then supplied to * hierarchy of the root test class and then supplied to
* {@link #loadContext(MergedContextConfiguration, boolean) loadContext()}. * {@link #loadContext(MergedContextConfiguration) loadContext()}.
* *
* <p>NOTE: As of Spring Framework 6.0, {@code SmartContextLoader} no longer * <p>NOTE: As of Spring Framework 6.0, {@code SmartContextLoader} no longer
* supports methods defined in the {@code ContextLoader} SPI. * supports methods defined in the {@code ContextLoader} SPI.
@@ -131,68 +131,36 @@ public interface SmartContextLoader extends ContextLoader {
* @return a new application context * @return a new application context
* @throws Exception if context loading failed * @throws Exception if context loading failed
* @see #processContextConfiguration(ContextConfigurationAttributes) * @see #processContextConfiguration(ContextConfigurationAttributes)
* @see #loadContextForAotProcessing(MergedContextConfiguration)
* @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry) * @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment() * @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
* @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)}
*/ */
@Deprecated
ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception; ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception;
/** /**
* Load a new {@linkplain ApplicationContext} based on the supplied * Load a new {@linkplain ApplicationContext} based on the supplied
* {@link MergedContextConfiguration}, configure the context, and return the * {@link MergedContextConfiguration}, configure the context, and return the
* context. * context.
* <p>Concrete implementations should register annotation configuration * <p>In contrast to {@link #loadContext(MergedContextConfiguration)}, this
* processors with bean factories of * method must <strong>not</strong>
* {@linkplain ApplicationContext application contexts} loaded by this
* {@code SmartContextLoader}. Beans will therefore automatically be
* candidates for annotation-based dependency injection using
* {@link org.springframework.beans.factory.annotation.Autowired @Autowired},
* {@link jakarta.annotation.Resource @Resource}, and
* {@link jakarta.inject.Inject @Inject}. In addition, concrete implementations
* should perform the following actions.
* <ul>
* <li>Set the parent {@code ApplicationContext} if appropriate (see
* {@link MergedContextConfiguration#getParent()}).</li>
* <li>Set the active bean definition profiles in the context's
* {@link org.springframework.core.env.Environment Environment} (see
* {@link MergedContextConfiguration#getActiveProfiles()}).</li>
* <li>Add test {@link org.springframework.core.env.PropertySource PropertySources}
* to the {@code Environment} (see
* {@link MergedContextConfiguration#getPropertySourceLocations()},
* {@link MergedContextConfiguration#getPropertySourceProperties()}, and
* {@link org.springframework.test.context.support.TestPropertySourceUtils
* TestPropertySourceUtils}).</li>
* <li>Invoke {@link org.springframework.context.ApplicationContextInitializer
* ApplicationContextInitializers} (see
* {@link MergedContextConfiguration#getContextInitializerClasses()}).</li>
* <li>Invoke {@link ContextCustomizer ContextCustomizers} (see
* {@link MergedContextConfiguration#getContextCustomizers()}).</li>
* <li>If the supplied {@code refresh} flag is {@code true},
* {@linkplain org.springframework.context.ConfigurableApplicationContext#refresh() * {@linkplain org.springframework.context.ConfigurableApplicationContext#refresh()
* refresh} the {@code ApplicationContext} and * refresh} the {@code ApplicationContext} or
* {@linkplain org.springframework.context.ConfigurableApplicationContext#registerShutdownHook() * {@linkplain org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
* register a JVM shutdown hook} for it.</li> * register a JVM shutdown hook} for it. Otherwise, this method should behave
* </ul> * identical to {@link #loadContext(MergedContextConfiguration)}.
* <p>The default implementation delegates to {@link #loadContext(MergedContextConfiguration)} * <p>The default implementation throws an {@link UnsupportedOperationException}.
* for backward compatibility. Concrete implementations should therefore * Concrete implementations must therefore override this method in order to
* override this method in order to honor the {@code refresh} flag which is * support AOT (ahead of time) processing.
* required for AOT (ahead of time) processing support.
* @param mergedConfig the merged context configuration to use to load the * @param mergedConfig the merged context configuration to use to load the
* application context * application context
* @param refresh whether to refresh the {@code ApplicationContext} and register
* a JVM shutdown hook for it
* @return a new application context * @return a new application context
* @throws Exception if context loading failed * @throws Exception if context loading failed
* @since 6.0 * @since 6.0
* @see #processContextConfiguration(ContextConfigurationAttributes)
* @see org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
* @see org.springframework.context.ConfigurableApplicationContext#getEnvironment()
* @see org.springframework.context.ConfigurableApplicationContext#registerShutdownHook()
*/ */
@SuppressWarnings("deprecation") default ApplicationContext loadContextForAotProcessing(MergedContextConfiguration mergedConfig) throws Exception {
default ApplicationContext loadContext(MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { throw new UnsupportedOperationException(
return loadContext(mergedConfig); "%s does not support loadContextForAotProcessing(MergedContextConfiguration)"
.formatted(getClass().getName()));
} }
/** /**
@@ -211,7 +179,7 @@ public interface SmartContextLoader extends ContextLoader {
/** /**
* {@code SmartContextLoader} does not support deprecated {@link ContextLoader} methods. * {@code SmartContextLoader} does not support deprecated {@link ContextLoader} methods.
* <p>Call {@link #loadContext(MergedContextConfiguration, boolean)} instead. * <p>Call {@link #loadContext(MergedContextConfiguration)} instead.
* @throws UnsupportedOperationException in this implementation * @throws UnsupportedOperationException in this implementation
* @since 6.0 * @since 6.0
*/ */
@@ -220,7 +188,7 @@ public interface SmartContextLoader extends ContextLoader {
default ApplicationContext loadContext(String... locations) throws Exception { default ApplicationContext loadContext(String... locations) throws Exception {
throw new UnsupportedOperationException(""" throw new UnsupportedOperationException("""
SmartContextLoader does not support the ContextLoader SPI. \ SmartContextLoader does not support the ContextLoader SPI. \
Call loadContext(MergedContextConfiguration, boolean) instead."""); Call loadContext(MergedContextConfiguration) instead.""");
} }
} }

View File

@@ -96,7 +96,7 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext
ApplicationContext applicationContext; ApplicationContext applicationContext;
if (contextLoader instanceof SmartContextLoader smartContextLoader) { if (contextLoader instanceof SmartContextLoader smartContextLoader) {
applicationContext = smartContextLoader.loadContext(mergedContextConfiguration, true); applicationContext = smartContextLoader.loadContext(mergedContextConfiguration);
} }
else { else {
String[] locations = mergedContextConfiguration.getLocations(); String[] locations = mergedContextConfiguration.getLocations();

View File

@@ -39,7 +39,7 @@ import org.springframework.util.Assert;
* and enclosing class hierarchy that is annotated with * and enclosing class hierarchy that is annotated with
* {@link ContextConfiguration @ContextConfiguration}, and the candidate that * {@link ContextConfiguration @ContextConfiguration}, and the candidate that
* supports the merged, processed configuration will be used to actually * supports the merged, processed configuration will be used to actually
* {@linkplain #loadContext load} the context. * {@linkplain #loadContext(MergedContextConfiguration) load} the context.
* *
* <p>Any reference to an <em>XML-based loader</em> can be interpreted to mean * <p>Any reference to an <em>XML-based loader</em> can be interpreted to mean
* a context loader that supports only XML configuration files or one that * a context loader that supports only XML configuration files or one that
@@ -176,13 +176,28 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
} }
/** /**
* Although this method is officially deprecated, for backward compatibility * Delegates to an appropriate candidate {@code SmartContextLoader} to load
* it delegates to {@link #loadContext(MergedContextConfiguration, boolean)}, * an {@link ApplicationContext}.
* supplying {@code true} for the {@code refresh} flag. * <p>Delegation is based on explicit knowledge of the implementations of the
* @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)} * default loaders for {@linkplain #getXmlLoader() XML configuration files and
* Groovy scripts} and {@linkplain #getAnnotationConfigLoader() annotated classes}.
* Specifically, the delegation algorithm is as follows:
* <ul>
* <li>If the resource locations in the supplied {@code MergedContextConfiguration}
* are not empty and the annotated classes are empty,
* the XML-based loader will load the {@code ApplicationContext}.</li>
* <li>If the annotated classes in the supplied {@code MergedContextConfiguration}
* are not empty and the resource locations are empty,
* the annotation-based loader will load the {@code ApplicationContext}.</li>
* </ul>
* @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
* @since 6.0
*/ */
@Override @Override
@Deprecated
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception { public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, true); return loadContext(mergedConfig, true);
} }
@@ -203,8 +218,6 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
* the annotation-based loader will load the {@code ApplicationContext}.</li> * the annotation-based loader will load the {@code ApplicationContext}.</li>
* </ul> * </ul>
* @param mergedConfig the merged context configuration to use to load the application context * @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
* @return a new application context * @return a new application context
* @throws IllegalArgumentException if the supplied merged configuration is {@code null} * @throws IllegalArgumentException if the supplied merged configuration is {@code null}
* @throws IllegalStateException if neither candidate loader is capable of loading an * @throws IllegalStateException if neither candidate loader is capable of loading an
@@ -212,7 +225,22 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
* @since 6.0 * @since 6.0
*/ */
@Override @Override
public ApplicationContext loadContext(MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { public ApplicationContext loadContextForAotProcessing(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, false);
}
/**
* Delegates to an appropriate candidate {@code SmartContextLoader} to load
* an {@link ApplicationContext}.
* @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
* @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
*/
private ApplicationContext loadContext(MergedContextConfiguration mergedConfig, boolean refresh) throws Exception {
Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null"); Assert.notNull(mergedConfig, "MergedContextConfiguration must not be null");
Assert.state(!(mergedConfig.hasLocations() && mergedConfig.hasClasses()), () -> String.format( Assert.state(!(mergedConfig.hasLocations() && mergedConfig.hasClasses()), () -> String.format(
@@ -242,7 +270,6 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
name(getXmlLoader()), name(getAnnotationConfigLoader()), mergedConfig)); name(getXmlLoader()), name(getAnnotationConfigLoader()), mergedConfig));
} }
private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) { private static void delegateProcessing(SmartContextLoader loader, ContextConfigurationAttributes configAttributes) {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Delegating to %s to process context configuration %s.", logger.debug(String.format("Delegating to %s to process context configuration %s.",
@@ -258,7 +285,7 @@ public abstract class AbstractDelegatingSmartContextLoader implements SmartConte
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig)); logger.debug(String.format("Delegating to %s to load context from %s.", name(loader), mergedConfig));
} }
return loader.loadContext(mergedConfig, refresh); return (refresh ? loader.loadContext(mergedConfig) : loader.loadContextForAotProcessing(mergedConfig));
} }
private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) { private boolean supports(SmartContextLoader loader, MergedContextConfiguration mergedConfig) {

View File

@@ -40,7 +40,7 @@ import org.springframework.util.StringUtils;
* <li>If instances of concrete subclasses are invoked via the * <li>If instances of concrete subclasses are invoked via the
* {@link org.springframework.test.context.SmartContextLoader SmartContextLoader} * {@link org.springframework.test.context.SmartContextLoader SmartContextLoader}
* SPI, the context will be loaded from the {@link MergedContextConfiguration} * SPI, the context will be loaded from the {@link MergedContextConfiguration}
* provided to {@link #loadContext(MergedContextConfiguration, boolean)}. In such * provided to {@link #loadContext(MergedContextConfiguration)}. In such
* cases, a {@code SmartContextLoader} will decide whether to load the context * cases, a {@code SmartContextLoader} will decide whether to load the context
* from <em>locations</em> or <em>annotated classes</em>.</li> * from <em>locations</em> or <em>annotated classes</em>.</li>
* </ul> * </ul>
@@ -54,26 +54,15 @@ import org.springframework.util.StringUtils;
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Phillip Webb * @author Phillip Webb
* @since 2.5 * @since 2.5
* @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(MergedContextConfiguration)
*/ */
public abstract class AbstractGenericContextLoader extends AbstractContextLoader { public abstract class AbstractGenericContextLoader extends AbstractContextLoader {
protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class); protected static final Log logger = LogFactory.getLog(AbstractGenericContextLoader.class);
/** /**
* Although this method is officially deprecated, for backward compatibility * Load a {@link GenericApplicationContext} for the supplied
* it delegates to {@link #loadContext(MergedContextConfiguration, boolean)}, * {@link MergedContextConfiguration}.
* supplying {@code true} for the {@code refresh} flag.
* @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)}
*/
@Override
@Deprecated
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, true);
}
/**
* Load a Spring ApplicationContext from the supplied {@link MergedContextConfiguration}.
* <p>Implementation details: * <p>Implementation details:
* <ul> * <ul>
* <li>Calls {@link #validateMergedContextConfiguration(MergedContextConfiguration)} * <li>Calls {@link #validateMergedContextConfiguration(MergedContextConfiguration)}
@@ -104,20 +93,52 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to * <li>Calls {@link #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)} to
* allow for customizing the context before it is refreshed.</li> * allow for customizing the context before it is refreshed.</li>
* <li>{@link ConfigurableApplicationContext#refresh Refreshes} the * <li>{@link ConfigurableApplicationContext#refresh Refreshes} the
* context and registers a JVM shutdown hook for it if the supplied the * context and registers a JVM shutdown hook for it.</li>
* {@code refresh} flag is {@code true}.</li>
* </ul> * </ul>
* @param mergedConfig the merged context configuration to use to load the * @param mergedConfig the merged context configuration to use to load the
* application context * application context
* @return a new application context
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
*/
@Override
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, true);
}
/**
* Load a {@link GenericApplicationContext} for the supplied
* {@link MergedContextConfiguration}.
* <p>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 behaves
* identical to {@link #loadContext(MergedContextConfiguration)}.
* @param mergedConfig the merged context configuration to use to load the
* application context
* @return a new application context
* @since 6.0
* @see org.springframework.test.context.SmartContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
*/
@Override
public final ApplicationContext loadContextForAotProcessing(
MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, false);
}
/**
* Load a {@link GenericApplicationContext} 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 * @param refresh whether to refresh the {@code ApplicationContext} and register
* a JVM shutdown hook for it * a JVM shutdown hook for it
* @return a new application context * @return a new application context
* @since 6.0 * @since 6.0
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration, boolean)
* @see GenericApplicationContext
*/ */
@Override private final GenericApplicationContext loadContext(
public final ConfigurableApplicationContext loadContext(
MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { MergedContextConfiguration mergedConfig, boolean refresh) throws Exception {
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
@@ -185,15 +206,15 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* context and registers a JVM shutdown hook for it.</li> * context and registers a JVM shutdown hook for it.</li>
* </ul> * </ul>
* <p><b>Note</b>: this method does not provide a means to set active bean definition * <p><b>Note</b>: this method does not provide a means to set active bean definition
* profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration, boolean)} * profiles for the loaded context. See {@link #loadContext(MergedContextConfiguration)}
* and {@link AbstractContextLoader#prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)} * and {@link AbstractContextLoader#prepareContext(ConfigurableApplicationContext, MergedContextConfiguration)}
* for an alternative. * for an alternative.
* @return a new application context * @return a new application context
* @since 2.5 * @since 2.5
* @see org.springframework.test.context.ContextLoader#loadContext * @see org.springframework.test.context.ContextLoader#loadContext
* @see GenericApplicationContext * @see GenericApplicationContext
* @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(MergedContextConfiguration)
* @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)} * @deprecated as of Spring Framework 6.0, in favor of {@link #loadContextForAotProcessing(MergedContextConfiguration)}
*/ */
@Deprecated @Deprecated
@Override @Override
@@ -238,8 +259,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* customize {@code GenericApplicationContext}'s standard settings. * customize {@code GenericApplicationContext}'s standard settings.
* @param context the context that should be prepared * @param context the context that should be prepared
* @since 2.5 * @since 2.5
* @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
* @see GenericApplicationContext#setAllowBeanDefinitionOverriding * @see GenericApplicationContext#setAllowBeanDefinitionOverriding
* @see GenericApplicationContext#setResourceLoader * @see GenericApplicationContext#setResourceLoader
* @see GenericApplicationContext#setId * @see GenericApplicationContext#setId
@@ -255,8 +275,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* to customize {@code DefaultListableBeanFactory}'s standard settings. * to customize {@code DefaultListableBeanFactory}'s standard settings.
* @param beanFactory the bean factory created by this {@code ContextLoader} * @param beanFactory the bean factory created by this {@code ContextLoader}
* @since 2.5 * @since 2.5
* @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
* @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding * @see DefaultListableBeanFactory#setAllowBeanDefinitionOverriding
* @see DefaultListableBeanFactory#setAllowEagerClassLoading * @see DefaultListableBeanFactory#setAllowEagerClassLoading
* @see DefaultListableBeanFactory#setAllowCircularReferences * @see DefaultListableBeanFactory#setAllowCircularReferences
@@ -280,7 +299,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* @param context the context into which the bean definitions should be loaded * @param context the context into which the bean definitions should be loaded
* @param mergedConfig the merged context configuration * @param mergedConfig the merged context configuration
* @since 3.1 * @since 3.1
* @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(MergedContextConfiguration)
*/ */
protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) { protected void loadBeanDefinitions(GenericApplicationContext context, MergedContextConfiguration mergedConfig) {
createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations()); createBeanDefinitionReader(context).loadBeanDefinitions(mergedConfig.getLocations());
@@ -293,7 +312,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* should be created * should be created
* @return a {@code BeanDefinitionReader} for the supplied context * @return a {@code BeanDefinitionReader} for the supplied context
* @since 2.5 * @since 2.5
* @see #loadContext(String...) * @see #loadContext(MergedContextConfiguration)
* @see #loadBeanDefinitions * @see #loadBeanDefinitions
* @see BeanDefinitionReader * @see BeanDefinitionReader
*/ */
@@ -307,8 +326,7 @@ public abstract class AbstractGenericContextLoader extends AbstractContextLoader
* to customize the application context. * to customize the application context.
* @param context the newly created application context * @param context the newly created application context
* @since 2.5 * @since 2.5
* @see #loadContext(MergedContextConfiguration, boolean) * @see #loadContext(MergedContextConfiguration)
* @see #loadContext(String...)
* @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration) * @see #customizeContext(ConfigurableApplicationContext, MergedContextConfiguration)
*/ */
protected void customizeContext(GenericApplicationContext context) { protected void customizeContext(GenericApplicationContext context) {

View File

@@ -65,19 +65,7 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
// SmartContextLoader // SmartContextLoader
/** /**
* Although this method is officially deprecated, for backward compatibility * Load a {@link GenericWebApplicationContext} for the supplied
* it delegates to {@link #loadContext(MergedContextConfiguration, boolean)},
* supplying {@code true} for the {@code refresh} flag.
* @deprecated as of Spring Framework 6.0, in favor of {@link #loadContext(MergedContextConfiguration, boolean)}
*/
@Override
@Deprecated
public final ApplicationContext loadContext(MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, true);
}
/**
* Load a Spring {@link WebApplicationContext} from the supplied
* {@link MergedContextConfiguration}. * {@link MergedContextConfiguration}.
* <p>Implementation details: * <p>Implementation details:
* <ul> * <ul>
@@ -108,15 +96,52 @@ public abstract class AbstractGenericWebContextLoader extends AbstractContextLoa
* </ul> * </ul>
* @param mergedConfig the merged context configuration to use to load the * @param mergedConfig the merged context configuration to use to load the
* application context * 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, true);
}
/**
* Load a {@link GenericWebApplicationContext} for the supplied
* {@link MergedContextConfiguration}.
* <p>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 behaves
* 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.SmartContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
* @see GenericWebApplicationContext
*/
@Override
public final ApplicationContext loadContextForAotProcessing(
MergedContextConfiguration mergedConfig) throws Exception {
return loadContext(mergedConfig, false);
}
/**
* 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 * @param refresh whether to refresh the {@code ApplicationContext} and register
* a JVM shutdown hook for it * a JVM shutdown hook for it
* @return a new web application context * @return a new web application context
* @since 6.0 * @since 6.0
* @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration, boolean) * @see org.springframework.test.context.SmartContextLoader#loadContext(MergedContextConfiguration)
* @see GenericWebApplicationContext * @see org.springframework.test.context.SmartContextLoader#loadContextForAotProcessing(MergedContextConfiguration)
*/ */
@Override private final GenericWebApplicationContext loadContext(
public final ConfigurableApplicationContext loadContext(
MergedContextConfiguration mergedConfig, boolean refresh) throws Exception { MergedContextConfiguration mergedConfig, boolean refresh) throws Exception {
Assert.isTrue(mergedConfig instanceof WebMergedContextConfiguration, Assert.isTrue(mergedConfig instanceof WebMergedContextConfiguration,

View File

@@ -20,6 +20,7 @@ import java.util.Arrays;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.test.context.MergedContextConfiguration; import org.springframework.test.context.MergedContextConfiguration;
@@ -48,7 +49,7 @@ class AnnotationConfigContextLoaderTests {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(),
new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, contextLoader); new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, contextLoader);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> contextLoader.loadContext(mergedConfig, true)) .isThrownBy(() -> contextLoader.loadContext(mergedConfig))
.withMessageContaining("does not support resource locations"); .withMessageContaining("does not support resource locations");
} }
@@ -56,30 +57,34 @@ class AnnotationConfigContextLoaderTests {
* @since 6.0 * @since 6.0
*/ */
@Test @Test
void loadContextHonorsRefreshTrue() throws Exception { void loadContextRefreshesContext() throws Exception {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration( MergedContextConfiguration mergedConfig = new MergedContextConfiguration(
AnnotatedFooConfigInnerClassTestCase.class, EMPTY_STRING_ARRAY, AnnotatedFooConfigInnerClassTestCase.class, EMPTY_STRING_ARRAY,
new Class<?>[] {AnnotatedFooConfigInnerClassTestCase.FooConfig.class}, new Class<?>[] {AnnotatedFooConfigInnerClassTestCase.FooConfig.class},
EMPTY_STRING_ARRAY, contextLoader); EMPTY_STRING_ARRAY, contextLoader);
ConfigurableApplicationContext context = contextLoader.loadContext(mergedConfig, true); ApplicationContext context = contextLoader.loadContext(mergedConfig);
assertThat(context).isNotNull(); assertThat(context).isInstanceOf(ConfigurableApplicationContext.class);
assertThat(context.isActive()).as("ApplicationContext is active").isTrue(); ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
assertThat(cac.isActive()).as("ApplicationContext is active").isTrue();
assertThat(context.getBean(String.class)).isEqualTo("foo"); assertThat(context.getBean(String.class)).isEqualTo("foo");
cac.close();
} }
/** /**
* @since 6.0 * @since 6.0
*/ */
@Test @Test
void loadContextHonorsRefreshFalse() throws Exception { void loadContextForAotProcessingDoesNotRefreshContext() throws Exception {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration( MergedContextConfiguration mergedConfig = new MergedContextConfiguration(
AnnotatedFooConfigInnerClassTestCase.class, EMPTY_STRING_ARRAY, AnnotatedFooConfigInnerClassTestCase.class, EMPTY_STRING_ARRAY,
new Class<?>[] {AnnotatedFooConfigInnerClassTestCase.FooConfig.class}, new Class<?>[] {AnnotatedFooConfigInnerClassTestCase.FooConfig.class},
EMPTY_STRING_ARRAY, contextLoader); EMPTY_STRING_ARRAY, contextLoader);
ConfigurableApplicationContext context = contextLoader.loadContext(mergedConfig, false); ApplicationContext context = contextLoader.loadContextForAotProcessing(mergedConfig);
assertThat(context).isNotNull(); assertThat(context).isInstanceOf(ConfigurableApplicationContext.class);
assertThat(context.isActive()).as("ApplicationContext is active").isFalse(); ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
assertThat(cac.isActive()).as("ApplicationContext is active").isFalse();
assertThat(Arrays.stream(context.getBeanDefinitionNames())).anyMatch(name -> name.contains("FooConfig")); assertThat(Arrays.stream(context.getBeanDefinitionNames())).anyMatch(name -> name.contains("FooConfig"));
cac.close();
} }
@Test @Test

View File

@@ -53,7 +53,7 @@ class CustomizedGenericXmlContextLoaderTests {
MergedContextConfiguration mergedConfig = MergedContextConfiguration mergedConfig =
new MergedContextConfiguration(getClass(), null, null, null, null); new MergedContextConfiguration(getClass(), null, null, null, null);
customLoader.loadContext(mergedConfig, true); customLoader.loadContext(mergedConfig);
assertThat(customizeInvoked).as("customizeContext() should have been invoked").isTrue(); assertThat(customizeInvoked).as("customizeContext() should have been invoked").isTrue();
} }

View File

@@ -100,7 +100,7 @@ class DelegatingSmartContextLoaderTests {
@Test @Test
void loadContextWithNullConfig() throws Exception { void loadContextWithNullConfig() throws Exception {
assertThatIllegalArgumentException().isThrownBy(() -> loader.loadContext(null, true)); assertThatIllegalArgumentException().isThrownBy(() -> loader.loadContext((MergedContextConfiguration) null));
} }
@Test @Test
@@ -108,7 +108,7 @@ class DelegatingSmartContextLoaderTests {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration( MergedContextConfiguration mergedConfig = new MergedContextConfiguration(
getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); getClass(), EMPTY_STRING_ARRAY, EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig, true)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageStartingWith("Neither") .withMessageStartingWith("Neither")
.withMessageContaining("was able to load an ApplicationContext from"); .withMessageContaining("was able to load an ApplicationContext from");
} }
@@ -121,7 +121,7 @@ class DelegatingSmartContextLoaderTests {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(),
new String[] {"test.xml"}, new Class<?>[] {getClass()}, EMPTY_STRING_ARRAY, loader); new String[] {"test.xml"}, new Class<?>[] {getClass()}, EMPTY_STRING_ARRAY, loader);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig, true)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageStartingWith("Neither") .withMessageStartingWith("Neither")
.withMessageContaining("declare either 'locations' or 'classes' but not both."); .withMessageContaining("declare either 'locations' or 'classes' but not both.");
} }
@@ -147,7 +147,7 @@ class DelegatingSmartContextLoaderTests {
private void assertApplicationContextLoadsAndContainsFooString(MergedContextConfiguration mergedConfig) private void assertApplicationContextLoadsAndContainsFooString(MergedContextConfiguration mergedConfig)
throws Exception { throws Exception {
ApplicationContext applicationContext = loader.loadContext(mergedConfig, true); ApplicationContext applicationContext = loader.loadContext(mergedConfig);
assertThat(applicationContext).isInstanceOf(ConfigurableApplicationContext.class); assertThat(applicationContext).isInstanceOf(ConfigurableApplicationContext.class);
assertThat(applicationContext.getBean(String.class)).isEqualTo("foo"); assertThat(applicationContext.getBean(String.class)).isEqualTo("foo");
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext; ConfigurableApplicationContext cac = (ConfigurableApplicationContext) applicationContext;
@@ -164,7 +164,7 @@ class DelegatingSmartContextLoaderTests {
new String[] {"classpath:/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml"}, new String[] {"classpath:/org/springframework/test/context/support/DelegatingSmartContextLoaderTests$XmlTestCase-context.xml"},
EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader); EMPTY_CLASS_ARRAY, EMPTY_STRING_ARRAY, loader);
assertApplicationContextLoadsWithoutRefresh(mergedConfig, "foo"); assertApplicationContextLoadsForAotProcessing(mergedConfig, "foo");
} }
/** /**
@@ -175,13 +175,13 @@ class DelegatingSmartContextLoaderTests {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(ConfigClassTestCase.class, MergedContextConfiguration mergedConfig = new MergedContextConfiguration(ConfigClassTestCase.class,
EMPTY_STRING_ARRAY, new Class<?>[] {ConfigClassTestCase.Config.class}, EMPTY_STRING_ARRAY, loader); EMPTY_STRING_ARRAY, new Class<?>[] {ConfigClassTestCase.Config.class}, EMPTY_STRING_ARRAY, loader);
assertApplicationContextLoadsWithoutRefresh(mergedConfig, "ConfigClassTestCase.Config"); assertApplicationContextLoadsForAotProcessing(mergedConfig, "ConfigClassTestCase.Config");
} }
private void assertApplicationContextLoadsWithoutRefresh(MergedContextConfiguration mergedConfig, private void assertApplicationContextLoadsForAotProcessing(MergedContextConfiguration mergedConfig,
String expectedBeanDefName) throws Exception { String expectedBeanDefName) throws Exception {
ApplicationContext context = loader.loadContext(mergedConfig, false); ApplicationContext context = loader.loadContextForAotProcessing(mergedConfig);
assertThat(context).isInstanceOf(ConfigurableApplicationContext.class); assertThat(context).isInstanceOf(ConfigurableApplicationContext.class);
ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context; ConfigurableApplicationContext cac = (ConfigurableApplicationContext) context;
assertThat(cac.isActive()).as("ApplicationContext is active").isFalse(); assertThat(cac.isActive()).as("ApplicationContext is active").isFalse();

View File

@@ -40,7 +40,7 @@ class GenericXmlContextLoaderTests {
MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY, MergedContextConfiguration mergedConfig = new MergedContextConfiguration(getClass(), EMPTY_STRING_ARRAY,
new Class<?>[] { getClass() }, EMPTY_STRING_ARRAY, loader); new Class<?>[] { getClass() }, EMPTY_STRING_ARRAY, loader);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig, true)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageContaining("does not support annotated classes"); .withMessageContaining("does not support annotated classes");
} }

View File

@@ -39,7 +39,7 @@ class AnnotationConfigWebContextLoaderTests {
new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, new String[] { "config.xml" }, EMPTY_CLASS_ARRAY, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY,
EMPTY_STRING_ARRAY, "resource/path", loader, null, null); EMPTY_STRING_ARRAY, "resource/path", loader, null, null);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig, true)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageContaining("does not support resource locations"); .withMessageContaining("does not support resource locations");
} }

View File

@@ -38,7 +38,7 @@ class GenericXmlWebContextLoaderTests {
new Class<?>[] { getClass() }, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, new Class<?>[] { getClass() }, null, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY, EMPTY_STRING_ARRAY,
"resource/path", loader, null, null); "resource/path", loader, null, null);
assertThatIllegalStateException() assertThatIllegalStateException()
.isThrownBy(() -> loader.loadContext(mergedConfig, true)) .isThrownBy(() -> loader.loadContext(mergedConfig))
.withMessageContaining("does not support annotated classes"); .withMessageContaining("does not support annotated classes");
} }