Skip to content

Commit 28c492c

Browse files
committed
Introduce RuntimeHintsUtils.registerResourceIfNecessary
This commit introduces a new registerResourceIfNecessary() method in RuntimeHintsUtils that simplifies the registration of hints for `classpath:` resources. Closes gh-29083
1 parent dd1e6b9 commit 28c492c

File tree

4 files changed

+82
-35
lines changed

4 files changed

+82
-35
lines changed

spring-core/src/main/java/org/springframework/aot/hint/support/RuntimeHintsUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,14 @@
1919
import java.util.function.Consumer;
2020

2121
import org.springframework.aot.hint.MemberCategory;
22+
import org.springframework.aot.hint.ResourceHints;
2223
import org.springframework.aot.hint.RuntimeHints;
2324
import org.springframework.aot.hint.TypeHint;
2425
import org.springframework.aot.hint.TypeHint.Builder;
2526
import org.springframework.core.annotation.AliasFor;
2627
import org.springframework.core.annotation.MergedAnnotation;
28+
import org.springframework.core.io.ClassPathResource;
29+
import org.springframework.core.io.Resource;
2730

2831
/**
2932
* Utility methods for runtime hints support code.
@@ -92,4 +95,18 @@ public static void registerAnnotationIfNecessary(RuntimeHints hints, MergedAnnot
9295
}
9396
}
9497

98+
/**
99+
* Determine if the supplied resource is a {@link ClassPathResource} that
100+
* {@linkplain Resource#exists() exists} and register the resource for run-time
101+
* availability accordingly.
102+
* @param hints the {@link RuntimeHints} instance to use
103+
* @param resource the resource to register
104+
* @see ResourceHints#registerPattern(String)
105+
*/
106+
public static void registerResourceIfNecessary(RuntimeHints hints, Resource resource) {
107+
if (resource instanceof ClassPathResource classPathResource && classPathResource.exists()) {
108+
hints.resources().registerPattern(classPathResource.getPath());
109+
}
110+
}
111+
95112
}

spring-core/src/test/java/org/springframework/aot/hint/support/RuntimeHintsUtilsTests.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.util.function.Consumer;
2222

23+
import org.junit.jupiter.api.Disabled;
2324
import org.junit.jupiter.api.Test;
2425

2526
import org.springframework.aot.hint.JdkProxyHint;
@@ -28,8 +29,11 @@
2829
import org.springframework.core.annotation.AliasFor;
2930
import org.springframework.core.annotation.MergedAnnotation;
3031
import org.springframework.core.annotation.MergedAnnotations;
32+
import org.springframework.core.io.ClassPathResource;
33+
import org.springframework.core.io.DescriptiveResource;
3134

3235
import static org.assertj.core.api.Assertions.assertThat;
36+
import static org.springframework.aot.hint.predicate.RuntimeHintsPredicates.resource;
3337

3438
/**
3539
* Tests for {@link RuntimeHintsUtils}.
@@ -41,6 +45,39 @@ class RuntimeHintsUtilsTests {
4145

4246
private final RuntimeHints hints = new RuntimeHints();
4347

48+
@Test
49+
void registerResourceIfNecessaryWithUnsupportedResourceType() {
50+
DescriptiveResource resource = new DescriptiveResource("bogus");
51+
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
52+
assertThat(this.hints.resources().resourcePatterns()).isEmpty();
53+
}
54+
55+
@Test
56+
void registerResourceIfNecessaryWithNonexistentClassPathResource() {
57+
ClassPathResource resource = new ClassPathResource("bogus", getClass());
58+
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
59+
assertThat(this.hints.resources().resourcePatterns()).isEmpty();
60+
}
61+
62+
@Test
63+
void registerResourceIfNecessaryWithExistingClassPathResource() {
64+
String path = "org/springframework/aot/hint/support";
65+
ClassPathResource resource = new ClassPathResource(path);
66+
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
67+
assertThat(resource().forResource(path)).accepts(this.hints);
68+
}
69+
70+
@Disabled("Disabled since ClassPathResource.getPath() does not honor its contract for relative resources")
71+
@Test
72+
void registerResourceIfNecessaryWithExistingRelativeClassPathResource() {
73+
String path = "org/springframework/aot/hint/support";
74+
ClassPathResource resource = new ClassPathResource("support", RuntimeHints.class);
75+
RuntimeHintsUtils.registerResourceIfNecessary(this.hints, resource);
76+
// This unfortunately fails since ClassPathResource.getPath() returns
77+
// "support" instead of "org/springframework/aot/hint/support".
78+
assertThat(resource().forResource(path)).accepts(this.hints);
79+
}
80+
4481
@Test
4582
@SuppressWarnings("deprecation")
4683
void registerSynthesizedAnnotation() {

spring-test/src/main/java/org/springframework/test/context/aot/hint/StandardTestRuntimeHints.java

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@
2020
import java.util.List;
2121

2222
import org.springframework.aot.hint.RuntimeHints;
23+
import org.springframework.aot.hint.support.RuntimeHintsUtils;
2324
import org.springframework.core.annotation.MergedAnnotations;
25+
import org.springframework.core.io.DefaultResourceLoader;
2426
import org.springframework.test.context.ActiveProfiles;
2527
import org.springframework.test.context.ActiveProfilesResolver;
2628
import org.springframework.test.context.ContextLoader;
@@ -50,12 +52,12 @@ class StandardTestRuntimeHints implements TestRuntimeHintsRegistrar {
5052
public void registerHints(MergedContextConfiguration mergedConfig, List<Class<?>> testClasses,
5153
RuntimeHints runtimeHints, ClassLoader classLoader) {
5254

53-
registerHintsForMergedContextConfiguration(runtimeHints, mergedConfig);
55+
registerHintsForMergedContextConfiguration(runtimeHints, classLoader, mergedConfig);
5456
testClasses.forEach(testClass -> registerHintsForActiveProfilesResolvers(runtimeHints, testClass));
5557
}
5658

5759
private void registerHintsForMergedContextConfiguration(
58-
RuntimeHints runtimeHints, MergedContextConfiguration mergedConfig) {
60+
RuntimeHints runtimeHints, ClassLoader classLoader, MergedContextConfiguration mergedConfig) {
5961

6062
// @ContextConfiguration(loader = ...)
6163
ContextLoader contextLoader = mergedConfig.getContextLoader();
@@ -68,14 +70,14 @@ private void registerHintsForMergedContextConfiguration(
6870
.forEach(clazz -> registerDeclaredConstructors(runtimeHints, clazz));
6971

7072
// @ContextConfiguration(locations = ...)
71-
registerClasspathResources(runtimeHints, mergedConfig.getLocations());
73+
registerClasspathResources(mergedConfig.getLocations(), runtimeHints, classLoader);
7274

7375
// @TestPropertySource(locations = ... )
74-
registerClasspathResources(runtimeHints, mergedConfig.getPropertySourceLocations());
76+
registerClasspathResources(mergedConfig.getPropertySourceLocations(), runtimeHints, classLoader);
7577

7678
// @WebAppConfiguration(value = ...)
7779
if (mergedConfig instanceof WebMergedContextConfiguration webConfig) {
78-
registerClasspathResourceDirectoryStructure(runtimeHints, webConfig.getResourceBasePath());
80+
registerClasspathResourceDirectoryStructure(webConfig.getResourceBasePath(), runtimeHints);
7981
}
8082
}
8183

@@ -94,16 +96,20 @@ private void registerDeclaredConstructors(RuntimeHints runtimeHints, Class<?> ty
9496
runtimeHints.reflection().registerType(type, INVOKE_DECLARED_CONSTRUCTORS);
9597
}
9698

97-
private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) {
98-
Arrays.stream(locations)
99-
.filter(location -> location.startsWith(CLASSPATH_URL_PREFIX))
100-
.map(this::cleanClasspathResource)
101-
.forEach(runtimeHints.resources()::registerPattern);
99+
private void registerClasspathResources(String[] paths, RuntimeHints runtimeHints, ClassLoader classLoader) {
100+
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
101+
Arrays.stream(paths)
102+
.filter(path -> path.startsWith(CLASSPATH_URL_PREFIX))
103+
.map(resourceLoader::getResource)
104+
.forEach(resource -> RuntimeHintsUtils.registerResourceIfNecessary(runtimeHints, resource));
102105
}
103106

104-
private void registerClasspathResourceDirectoryStructure(RuntimeHints runtimeHints, String directory) {
107+
private void registerClasspathResourceDirectoryStructure(String directory, RuntimeHints runtimeHints) {
105108
if (directory.startsWith(CLASSPATH_URL_PREFIX)) {
106-
String pattern = cleanClasspathResource(directory);
109+
String pattern = directory.substring(CLASSPATH_URL_PREFIX.length());
110+
if (pattern.startsWith(SLASH)) {
111+
pattern = pattern.substring(1);
112+
}
107113
if (!pattern.endsWith(SLASH)) {
108114
pattern += SLASH;
109115
}
@@ -112,12 +118,4 @@ private void registerClasspathResourceDirectoryStructure(RuntimeHints runtimeHin
112118
}
113119
}
114120

115-
private String cleanClasspathResource(String location) {
116-
location = location.substring(CLASSPATH_URL_PREFIX.length());
117-
if (location.startsWith(SLASH)) {
118-
location = location.substring(1);
119-
}
120-
return location;
121-
}
122-
123121
}

spring-test/src/main/java/org/springframework/test/context/jdbc/SqlScriptsTestExecutionListener.java

Lines changed: 10 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@
2828
import org.apache.commons.logging.LogFactory;
2929

3030
import org.springframework.aot.hint.RuntimeHints;
31+
import org.springframework.aot.hint.support.RuntimeHintsUtils;
3132
import org.springframework.context.ApplicationContext;
3233
import org.springframework.core.annotation.AnnotatedElementUtils;
3334
import org.springframework.core.io.ByteArrayResource;
3435
import org.springframework.core.io.ClassPathResource;
36+
import org.springframework.core.io.DefaultResourceLoader;
3537
import org.springframework.core.io.Resource;
3638
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
3739
import org.springframework.lang.NonNull;
@@ -148,10 +150,10 @@ public void afterTestMethod(TestContext testContext) {
148150
@Override
149151
public void processAheadOfTime(Class<?> testClass, RuntimeHints runtimeHints, ClassLoader classLoader) {
150152
getSqlAnnotationsFor(testClass).forEach(sql ->
151-
registerClasspathResources(runtimeHints, getScripts(sql, testClass, null, true)));
153+
registerClasspathResources(getScripts(sql, testClass, null, true), runtimeHints, classLoader));
152154
getSqlMethods(testClass).forEach(testMethod ->
153155
getSqlAnnotationsFor(testMethod).forEach(sql ->
154-
registerClasspathResources(runtimeHints, getScripts(sql, testClass, testMethod, false))));
156+
registerClasspathResources(getScripts(sql, testClass, testMethod, false), runtimeHints, classLoader)));
155157
}
156158

157159
/**
@@ -390,19 +392,12 @@ private Stream<Method> getSqlMethods(Class<?> testClass) {
390392
return Arrays.stream(ReflectionUtils.getUniqueDeclaredMethods(testClass, sqlMethodFilter));
391393
}
392394

393-
private void registerClasspathResources(RuntimeHints runtimeHints, String... locations) {
394-
Arrays.stream(locations)
395-
.filter(location -> location.startsWith(CLASSPATH_URL_PREFIX))
396-
.map(this::cleanClasspathResource)
397-
.forEach(runtimeHints.resources()::registerPattern);
398-
}
399-
400-
private String cleanClasspathResource(String location) {
401-
location = location.substring(CLASSPATH_URL_PREFIX.length());
402-
if (location.startsWith(SLASH)) {
403-
location = location.substring(1);
404-
}
405-
return location;
395+
private void registerClasspathResources(String[] paths, RuntimeHints runtimeHints, ClassLoader classLoader) {
396+
DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader);
397+
Arrays.stream(paths)
398+
.filter(path -> path.startsWith(CLASSPATH_URL_PREFIX))
399+
.map(resourceLoader::getResource)
400+
.forEach(resource -> RuntimeHintsUtils.registerResourceIfNecessary(runtimeHints, resource));
406401
}
407402

408403
}

0 commit comments

Comments
 (0)