Allow test classes to provide runtime hints via declarative mechanisms

Prior to this commit, it was possible to register hints for individual
test classes programmatically via the
org.springframework.test.context.aot.TestRuntimeHintsRegistrar SPI;
however, that requires that a custom TestRuntimeHintsRegistrar be
registered via "META-INF/spring/aot.factories". In addition,
implementing a TestRuntimeHintsRegistrar is more cumbersome than using
the core mechanisms such as @Reflective, @ImportRuntimeHints, and
@RegisterReflectionForBinding.

This commit address this by introducing support for @Reflective and
@ImportRuntimeHints on test classes. @RegisterReflectionForBinding
support is available automatically since it is an extension of the
@Reflective mechanism.

Closes gh-29455
This commit is contained in:
Sam Brannen
2022-11-11 17:07:29 +01:00
parent 1b61217705
commit ae3bec5d57
3 changed files with 190 additions and 1 deletions

View File

@@ -16,7 +16,10 @@
package org.springframework.test.context.aot;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
@@ -30,12 +33,19 @@ import org.springframework.aot.generate.GeneratedClasses;
import org.springframework.aot.generate.GeneratedFiles;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.hint.annotation.ReflectiveRuntimeHintsRegistrar;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.aot.AotServices;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.context.aot.ApplicationContextAotGenerator;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.annotation.MergedAnnotation;
import org.springframework.core.annotation.MergedAnnotations;
import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
import org.springframework.core.log.LogMessage;
import org.springframework.javapoet.ClassName;
import org.springframework.test.context.BootstrapUtils;
@@ -117,16 +127,31 @@ public class TestContextAotGenerator {
try {
resetAotFactories();
Set<Class<? extends RuntimeHintsRegistrar>> coreRuntimeHintsRegistrarClasses = new LinkedHashSet<>();
ReflectiveRuntimeHintsRegistrar reflectiveRuntimeHintsRegistrar = new ReflectiveRuntimeHintsRegistrar();
MultiValueMap<MergedContextConfiguration, Class<?>> mergedConfigMappings = new LinkedMultiValueMap<>();
ClassLoader classLoader = getClass().getClassLoader();
testClasses.forEach(testClass -> {
MergedContextConfiguration mergedConfig = buildMergedContextConfiguration(testClass);
mergedConfigMappings.add(mergedConfig, testClass);
collectRuntimeHintsRegistrarClasses(testClass, coreRuntimeHintsRegistrarClasses);
reflectiveRuntimeHintsRegistrar.registerRuntimeHints(this.runtimeHints, testClass);
this.testRuntimeHintsRegistrars.forEach(registrar ->
registrar.registerHints(this.runtimeHints, testClass, classLoader));
});
MultiValueMap<ClassName, Class<?>> initializerClassMappings = processAheadOfTime(mergedConfigMappings);
coreRuntimeHintsRegistrarClasses.stream()
.map(BeanUtils::instantiateClass)
.forEach(registrar -> {
if (logger.isTraceEnabled()) {
logger.trace("Processing RuntimeHints contribution from test class [%s]"
.formatted(registrar.getClass().getCanonicalName()));
}
registrar.registerHints(this.runtimeHints, classLoader);
});
MultiValueMap<ClassName, Class<?>> initializerClassMappings = processAheadOfTime(mergedConfigMappings);
generateAotTestContextInitializerMappings(initializerClassMappings);
generateAotTestAttributeMappings();
}
@@ -135,6 +160,25 @@ public class TestContextAotGenerator {
}
}
/**
* Collect all {@link RuntimeHintsRegistrar} classes declared via
* {@link ImportRuntimeHints @ImportRuntimeHints} on the supplied test class
* and add them to the supplied {@link Set}.
* @param testClass the test class on which to search for {@code @ImportRuntimeHints}
* @param coreRuntimeHintsRegistrarClasses the set of registrar classes
*/
private void collectRuntimeHintsRegistrarClasses(
Class<?> testClass, Set<Class<? extends RuntimeHintsRegistrar>> coreRuntimeHintsRegistrarClasses) {
MergedAnnotations.from(testClass, SearchStrategy.TYPE_HIERARCHY)
.stream(ImportRuntimeHints.class)
.filter(MergedAnnotation::isPresent)
.map(MergedAnnotation::synthesize)
.map(ImportRuntimeHints::value)
.flatMap(Arrays::stream)
.forEach(coreRuntimeHintsRegistrarClasses::add);
}
private void resetAotFactories() {
AotTestAttributesFactory.reset();
AotTestContextInitializersFactory.reset();