diff --git a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java index 03fc7eabc55..fc2a4cc8326 100644 --- a/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java +++ b/spring-test/src/main/java/org/springframework/test/context/CacheAwareContextLoaderDelegate.java @@ -36,6 +36,31 @@ import org.springframework.test.annotation.DirtiesContext.HierarchyMode; */ public interface CacheAwareContextLoaderDelegate { + /** + * The default failure threshold for errors encountered while attempting to + * load an application context: {@value}. + * @since 6.1 + * @see #CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME + */ + int DEFAULT_CONTEXT_FAILURE_THRESHOLD = 1; + + /** + * System property used to configure the failure threshold for errors + * encountered while attempting to load an application context: {@value}. + *
May alternatively be configured via the + * {@link org.springframework.core.SpringProperties} mechanism. + *
Implementations of {@code CacheAwareContextLoaderDelegate} are not + * required to support this feature. Consult the documentation of the + * corresponding implementation for details. Note, however, that the standard + * {@code CacheAwareContextLoaderDelegate} implementation in Spring supports + * this feature. + * @since 6.1 + * @see #DEFAULT_CONTEXT_FAILURE_THRESHOLD + * @see #loadContext(MergedContextConfiguration) + */ + String CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME = "spring.test.context.failure.threshold"; + + /** * Determine if the {@linkplain ApplicationContext application context} for * the supplied {@link MergedContextConfiguration} has been loaded (i.e., @@ -72,6 +97,13 @@ public interface CacheAwareContextLoaderDelegate { * mechanism, catch any exception thrown by the {@link ContextLoader}, and * delegate to each of the configured failure processors to process the context * load failure if the exception is an instance of {@link ContextLoadException}. + *
As of Spring Framework 6.1, implementations of this method are encouraged + * to support the failure threshold feature. Specifically, if repeated + * attempts are made to load an application context and that application + * context consistently fails to load — for example, due to a configuration + * error that prevents the context from successfully loading — this + * method should preemptively throw an {@link IllegalStateException} if the + * configured failure threshold has been exceeded. *
The cache statistics should be logged by invoking * {@link org.springframework.test.context.cache.ContextCache#logStatistics()}. * @param mergedConfig the merged context configuration to use to load the @@ -81,6 +113,7 @@ public interface CacheAwareContextLoaderDelegate { * the application context * @see #isContextLoaded * @see #closeContext + * @see #CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME */ ApplicationContext loadContext(MergedContextConfiguration mergedConfig); diff --git a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java index b89f4774a7d..659361d017d 100644 --- a/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/cache/ContextCacheUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2023 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. @@ -17,10 +17,11 @@ package org.springframework.test.context.cache; import org.springframework.core.SpringProperties; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; import org.springframework.util.StringUtils; /** - * Collection of utilities for working with {@link ContextCache ContextCaches}. + * Collection of utilities for working with context caching. * * @author Sam Brannen * @since 4.3 @@ -30,17 +31,40 @@ public abstract class ContextCacheUtils { /** * Retrieve the maximum size of the {@link ContextCache}. *
Uses {@link SpringProperties} to retrieve a system property or Spring - * property named {@code spring.test.context.cache.maxSize}. - *
Falls back to the value of the {@link ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE} + * property named {@value ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME}. + *
Defaults to {@value ContextCache#DEFAULT_MAX_CONTEXT_CACHE_SIZE} * if no such property has been set or if the property is not an integer. * @return the maximum size of the context cache * @see ContextCache#MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME */ public static int retrieveMaxCacheSize() { + String propertyName = ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME; + int defaultValue = ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE; + return retrieveProperty(propertyName, defaultValue); + } + + /** + * Retrieve the failure threshold for application context loading. + *
Uses {@link SpringProperties} to retrieve a system property or Spring + * property named {@value CacheAwareContextLoaderDelegate#CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME}. + *
Defaults to {@value CacheAwareContextLoaderDelegate#DEFAULT_CONTEXT_FAILURE_THRESHOLD} + * if no such property has been set or if the property is not an integer. + * @return the failure threshold + * @since 6.1 + * @see CacheAwareContextLoaderDelegate#CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME + * @see CacheAwareContextLoaderDelegate#DEFAULT_CONTEXT_FAILURE_THRESHOLD + */ + public static int retrieveContextFailureThreshold() { + String propertyName = CacheAwareContextLoaderDelegate.CONTEXT_FAILURE_THRESHOLD_PROPERTY_NAME; + int defaultValue = CacheAwareContextLoaderDelegate.DEFAULT_CONTEXT_FAILURE_THRESHOLD; + return retrieveProperty(propertyName, defaultValue); + } + + private static int retrieveProperty(String key, int defaultValue) { try { - String maxSize = SpringProperties.getProperty(ContextCache.MAX_CONTEXT_CACHE_SIZE_PROPERTY_NAME); - if (StringUtils.hasText(maxSize)) { - return Integer.parseInt(maxSize.trim()); + String value = SpringProperties.getProperty(key); + if (StringUtils.hasText(value)) { + return Integer.parseInt(value.trim()); } } catch (Exception ex) { @@ -48,7 +72,7 @@ public abstract class ContextCacheUtils { } // Fallback - return ContextCache.DEFAULT_MAX_CONTEXT_CACHE_SIZE; + return defaultValue; } } 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 d5b3426476b..722b0890a0c 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 @@ -62,13 +62,6 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext private static final Log logger = LogFactory.getLog(DefaultCacheAwareContextLoaderDelegate.class); - /** - * The default failure threshold for errors encountered while attempting to - * load an {@link ApplicationContext}: {@value}. - * @since 6.1 - */ - private static final int DEFAULT_FAILURE_THRESHOLD = 1; - /** * Default static cache of Spring application contexts. */ @@ -114,9 +107,17 @@ public class DefaultCacheAwareContextLoaderDelegate implements CacheAwareContext * @see #DefaultCacheAwareContextLoaderDelegate() */ public DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache) { + this(contextCache, ContextCacheUtils.retrieveContextFailureThreshold()); + } + + /** + * @since 6.1 + */ + DefaultCacheAwareContextLoaderDelegate(ContextCache contextCache, int failureThreshold) { Assert.notNull(contextCache, "ContextCache must not be null"); + Assert.isTrue(failureThreshold > 0, "'failureThreshold' must be positive"); this.contextCache = contextCache; - this.failureThreshold = DEFAULT_FAILURE_THRESHOLD; + this.failureThreshold = failureThreshold; }