diff --git a/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java index 9668f1d891a2..8ba12ff87a57 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java @@ -16,14 +16,19 @@ package org.springframework.aot.hint.support; +import java.util.Arrays; import java.util.function.Consumer; import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ResourceHints; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeHint; import org.springframework.aot.hint.TypeHint.Builder; import org.springframework.core.annotation.AliasFor; import org.springframework.core.annotation.MergedAnnotation; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; /** * Utility methods for runtime hints support code. @@ -92,4 +97,25 @@ public static void registerAnnotationIfNecessary(RuntimeHints hints, MergedAnnot } } + /** + * Register that the supplied resource should be made available at runtime. + *

If the supplied resource is not a {@link ClassPathResource}, it will + * not be registered. + * @param hints the {@link RuntimeHints} instance to use + * @param resource the resource to register + * @throws IllegalArgumentException if the supplied resource does not + * {@linkplain Resource#exists() exist} + * @see ResourceHints#registerPattern(String) + */ + public static void registerResource(RuntimeHints hints, Resource resource) { + if (resource instanceof ClassPathResource classPathResource) { + Assert.isTrue(resource.exists(), () -> "Resource does not exist: " + resource); + hints.resources().registerPattern(classPathResource.getPath()); + } + } + + public static void registerResources(RuntimeHints hints, Resource... resources) { + Arrays.stream(resources).forEach(resource -> registerResource(hints, resource)); + } + } diff --git a/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java b/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java index b545bba6f7c4..df79accbc7c5 100644 --- a/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java +++ b/spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java @@ -20,7 +20,9 @@ import java.util.List; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.support.RuntimeHintsUtils; import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfilesResolver; import org.springframework.test.context.ContextLoader; @@ -50,12 +52,12 @@ class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar { public void registerHints(MergedContextConfiguration mergedConfig, List> testClasses, RuntimeHints runtimeHints, ClassLoader classLoader) { - registerHintsForMergedContextConfiguration(runtimeHints, mergedConfig); + registerHintsForMergedContextConfiguration(runtimeHints, classLoader, mergedConfig); testClasses.forEach(testClass -> registerHintsForActiveProfilesResolvers(runtimeHints, testClass)); } private void registerHintsForMergedContextConfiguration( - RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig) { + RuntimeHints runtimeHints, ClassLoader classLoader, MergedContextConfiguration mergedConfig) { // @ContextConfiguration(loader = ...) ContextLoader contextLoader = mergedConfig.getContextLoader(); @@ -68,10 +70,10 @@ private void registerHintsForMergedContextConfiguration( .forEach(clazz -> registerDeclaredConstructors(runtimeHints, clazz)); // @ContextConfiguration(locations = ...) - registerClasspathResources(runtimeHints, mergedConfig.getLocations()); + registerClasspathResources(runtimeHints, classLoader, mergedConfig.getLocations()); // @TestPropertySource(locations = ... ) - registerClasspathResources(runtimeHints, mergedConfig.getPropertySourceLocations()); + registerClasspathResources(runtimeHints, classLoader, mergedConfig.getPropertySourceLocations()); // @WebAppConfiguration(value = ...) if (mergedConfig instanceof WebMergedContextConfiguration webConfig) { @@ -94,11 +96,14 @@ private void registerDeclaredConstructors(RuntimeHints runtimeHints, Class ty runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS); } - private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) { + private void registerClasspathResources(RuntimeHints runtimeHints, ClassLoader classLoader, String... locations) { + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader); Arrays.stream(locations) + // For the sake of efficiency, we still filter out locations that are not classpath: resources, + // even though the current implementation of RuntimeHintsUtils#registerResource handles that. .filter(location -> location.startsWith(CLASSPATH_URL_PREFIX)) - .map(this::cleanClasspathResource) - .forEach(runtimeHints.resources()::registerPattern); + .map(resourceLoader::getResource) + .forEach(resource -> RuntimeHintsUtils.registerResource(runtimeHints, resource)); } private void registerClasspathResourceDirectoryStructure(RuntimeHints runtimeHints, String directory) { diff --git a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java index 0b16e4f216b9..faca617b6f0b 100644 --- a/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java +++ b/spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java @@ -28,10 +28,12 @@ import org.apache.commons.logging.LogFactory; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.support.RuntimeHintsUtils; import org.springframework.context.ApplicationContext; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.io.ByteArrayResource; import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator; import org.springframework.lang.NonNull; @@ -148,10 +150,10 @@ public void afterTestMethod(TestContext testContext) { @Override public void processAheadOfTime(Class testClass, RuntimeHints runtimeHints, ClassLoader classLoader) { getSqlAnnotationsFor(testClass).forEach(sql -> - registerClasspathResources(runtimeHints, getScripts(sql, testClass, null, true))); + registerClasspathResources(runtimeHints, classLoader, getScripts(sql, testClass, null, true))); getSqlMethods(testClass).forEach(testMethod -> getSqlAnnotationsFor(testMethod).forEach(sql -> - registerClasspathResources(runtimeHints, getScripts(sql, testClass, testMethod, false)))); + registerClasspathResources(runtimeHints, classLoader, getScripts(sql, testClass, testMethod, false)))); } /** @@ -390,19 +392,10 @@ private Stream getSqlMethods(Class testClass) { return Arrays.stream(ReflectionUtils.getUniqueDeclaredMethods(testClass, sqlMethodFilter)); } - private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) { - Arrays.stream(locations) - .filter(location -> location.startsWith(CLASSPATH_URL_PREFIX)) - .map(this::cleanClasspathResource) - .forEach(runtimeHints.resources()::registerPattern); - } - - private String cleanClasspathResource(String location) { - location = location.substring(CLASSPATH_URL_PREFIX.length()); - if (!location.startsWith(SLASH)) { - location = SLASH + location; - } - return location; + private void registerClasspathResources(RuntimeHints runtimeHints, ClassLoader classLoader, String... locations) { + DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader); + RuntimeHintsUtils.registerResources(runtimeHints, + TestContextResourceUtils.convertToResources(resourceLoader, locations)); } } diff --git a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java index 8124709a86b2..3efa0409c203 100644 --- a/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java +++ b/spring-test/src/test/java/org/springframework/test/context/aot/TestContextAotGeneratorTests.java @@ -181,11 +181,11 @@ private static void assertRuntimeHints(RuntimeHints runtimeHints) { ).forEach(type -> assertReflectionRegistered(runtimeHints, type, INVOKE_DECLARED_CONSTRUCTORS)); // @ContextConfiguration(locations = ...) - assertThat(resource().forResource("/org/springframework/test/context/aot/samples/xml/test-config.xml")) + assertThat(resource().forResource("org/springframework/test/context/aot/samples/xml/test-config.xml")) .accepts(runtimeHints); // @TestPropertySource(locations = ...) - assertThat(resource().forResource("/org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.properties")) + assertThat(resource().forResource("org/springframework/test/context/aot/samples/basic/BasicSpringVintageTests.properties")) .accepts(runtimeHints); // @WebAppConfiguration(value = ...) @@ -193,9 +193,9 @@ private static void assertRuntimeHints(RuntimeHints runtimeHints) { assertThat(resource().forResource("/META-INF/web-resources/WEB-INF/views/home.jsp")).accepts(runtimeHints); // @Sql(scripts = ...) - assertThat(resource().forResource("/org/springframework/test/context/jdbc/schema.sql")) + assertThat(resource().forResource("org/springframework/test/context/jdbc/schema.sql")) .accepts(runtimeHints); - assertThat(resource().forResource("/org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests.test.sql")) + assertThat(resource().forResource("org/springframework/test/context/aot/samples/jdbc/SqlScriptsSpringJupiterTests.test.sql")) .accepts(runtimeHints); }