From 63fac1b7c871ce3c8129baf12705ee8e15a01f5c Mon Sep 17 00:00:00 2001 From: Sam Brannen Date: Sun, 10 Oct 2021 22:40:26 +0200 Subject: [PATCH] Allow default CacheAwareContextLoaderDelegate configuration via system property Prior to this commit, the default CacheAwareContextLoaderDelegate could be configured by extending AbstractTestContextBootstrapper and overriding getCacheAwareContextLoaderDelegate(); however, this required that the user configure the custom TestContextBootstrapper via @BootstrapWith. This commit introduces a new "spring.test.context.default.CacheAwareContextLoaderDelegate" property that can be configured via a JVM system property or via the SpringProperties mechanism. BootstrapUtils uses this new property to load the default CacheAwareContextLoaderDelegate. If the property is not defined, BootstrapUtils will fall back to creating a DefaultCacheAwareContextLoaderDelegate as it did previously. This allows third parties to configure the default CacheAwareContextLoaderDelegate transparently for the user -- for example, to intercept context loading in order to load the context in a different manner -- for example, to make use of ahead of time (AOT) techniques for implementing a different type of ApplicationContext at build time. Closes gh-27540 --- .../test/context/BootstrapUtils.java | 26 +++-- .../CacheAwareContextLoaderDelegate.java | 16 +++- ...tCacheAwareContextLoaderDelegateTests.java | 95 +++++++++++++++++++ 3 files changed, 127 insertions(+), 10 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/context/support/CustomDefaultCacheAwareContextLoaderDelegateTests.java diff --git a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java index ff88ade862..0336e4d513 100644 --- a/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java +++ b/spring-test/src/main/java/org/springframework/test/context/BootstrapUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2021 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. @@ -25,9 +25,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.BeanUtils; +import org.springframework.core.SpringProperties; import org.springframework.lang.Nullable; import org.springframework.test.context.TestContextAnnotationUtils.AnnotationDescriptor; import org.springframework.util.ClassUtils; +import org.springframework.util.StringUtils; /** * {@code BootstrapUtils} is a collection of utility methods to assist with @@ -65,7 +67,11 @@ abstract class BootstrapUtils { /** * Create the {@code BootstrapContext} for the specified {@linkplain Class test class}. *

Uses reflection to create a {@link org.springframework.test.context.support.DefaultBootstrapContext} - * that uses a {@link org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate}. + * that uses a default {@link CacheAwareContextLoaderDelegate} — configured + * via the {@link CacheAwareContextLoaderDelegate#DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_PROPERTY_NAME} + * system property or falling back to the + * {@link org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate} + * if the system property is not defined. * @param testClass the test class for which the bootstrap context should be created * @return a new {@code BootstrapContext}; never {@code null} */ @@ -90,19 +96,21 @@ abstract class BootstrapUtils { @SuppressWarnings("unchecked") private static CacheAwareContextLoaderDelegate createCacheAwareContextLoaderDelegate() { - Class clazz = null; + String className = SpringProperties.getProperty( + CacheAwareContextLoaderDelegate.DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_PROPERTY_NAME); + className = (StringUtils.hasText(className) ? className.trim() : + DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME); try { - clazz = (Class) ClassUtils.forName( - DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_CLASS_NAME, BootstrapUtils.class.getClassLoader()); - + Class clazz = + (Class) ClassUtils.forName( + className, BootstrapUtils.class.getClassLoader()); if (logger.isDebugEnabled()) { - logger.debug(String.format("Instantiating CacheAwareContextLoaderDelegate from class [%s]", - clazz.getName())); + logger.debug(String.format("Instantiating CacheAwareContextLoaderDelegate from class [%s]", className)); } return BeanUtils.instantiateClass(clazz, CacheAwareContextLoaderDelegate.class); } catch (Throwable ex) { - throw new IllegalStateException("Could not load CacheAwareContextLoaderDelegate [" + clazz + "]", ex); + throw new IllegalStateException("Could not create CacheAwareContextLoaderDelegate [" + className + "]", ex); } } 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 fd9e8bb697..d05da0c51d 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 @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -36,6 +36,20 @@ import org.springframework.test.annotation.DirtiesContext.HierarchyMode; */ public interface CacheAwareContextLoaderDelegate { + /** + * System property used to configure the fully qualified class name of the + * default {@code CacheAwareContextLoaderDelegate}. + *

May alternatively be configured via the + * {@link org.springframework.core.SpringProperties} mechanism. + *

If this property is not defined, the + * {@link org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate + * DefaultCacheAwareContextLoaderDelegate} will be used as the default. + * @since 5.3.11 + */ + String DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_PROPERTY_NAME = + "spring.test.context.default.CacheAwareContextLoaderDelegate"; + + /** * Determine if the {@linkplain ApplicationContext application context} for * the supplied {@link MergedContextConfiguration} has been loaded (i.e., diff --git a/spring-test/src/test/java/org/springframework/test/context/support/CustomDefaultCacheAwareContextLoaderDelegateTests.java b/spring-test/src/test/java/org/springframework/test/context/support/CustomDefaultCacheAwareContextLoaderDelegateTests.java new file mode 100644 index 0000000000..963b616354 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/context/support/CustomDefaultCacheAwareContextLoaderDelegateTests.java @@ -0,0 +1,95 @@ +/* + * Copyright 2002-2021 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.test.context.support; + +import org.junit.jupiter.api.Test; +import org.junit.platform.testkit.engine.EngineTestKit; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.support.GenericApplicationContext; +import org.springframework.core.SpringProperties; +import org.springframework.test.context.CacheAwareContextLoaderDelegate; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; + +/** + * Integration tests for configuring a custom default {@link CacheAwareContextLoaderDelegate} + * via {@link SpringProperties}. + * + * @author sbrannen + * @since 5.3.11 + */ +class CustomDefaultCacheAwareContextLoaderDelegateTests { + + @Test + void customDefaultCacheAwareContextLoaderDelegateConfiguredViaSpringProperties() { + String key = CacheAwareContextLoaderDelegate.DEFAULT_CACHE_AWARE_CONTEXT_LOADER_DELEGATE_PROPERTY_NAME; + + try { + SpringProperties.setProperty(key, AotCacheAwareContextLoaderDelegate.class.getName()); + + EngineTestKit.engine("junit-jupiter")// + .selectors(selectClass(TestCase.class))// + .execute()// + .testEvents()// + .assertStatistics(stats -> stats.started(1).succeeded(1).failed(0)); + } + finally { + SpringProperties.setProperty(key, null); + } + } + + + @SpringJUnitConfig + static class TestCase { + + @Test + void test(@Autowired String foo) { + // foo will be "bar" unless the AotCacheAwareContextLoaderDelegate is registered. + assertThat(foo).isEqualTo("AOT"); + } + + + @Configuration + static class Config { + + @Bean + String foo() { + return "bar"; + } + } + } + + static class AotCacheAwareContextLoaderDelegate extends DefaultCacheAwareContextLoaderDelegate { + + @Override + protected ApplicationContext loadContextInternal(MergedContextConfiguration mergedContextConfiguration) { + GenericApplicationContext applicationContext = new GenericApplicationContext(); + applicationContext.registerBean("foo", String.class, () -> "AOT"); + applicationContext.refresh(); + return applicationContext; + } + } + +}