From d6d4b98780132fff58ba610d2cde53419d32ce4a Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 14 Jun 2022 14:00:08 +0200 Subject: [PATCH] Add ifPresent utility methods on RuntimeHints This commit adds a utility method to register a type for reflection if it is available on the classpath. It also adds a method to register a resource pattern if a given location is available. Closes gh-28617 --- .../aot/hint/ReflectionHints.java | 17 +++++++++++++++++ .../aot/hint/ResourceHints.java | 18 ++++++++++++++++++ .../aot/hint/ReflectionHintsTests.java | 19 +++++++++++++++++++ .../aot/hint/ResourceHintsTests.java | 19 +++++++++++++++++++ 4 files changed, 73 insertions(+) diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java index a2d51bd27c..c5386cfdbb 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ReflectionHints.java @@ -30,6 +30,7 @@ import java.util.stream.Stream; import org.springframework.aot.hint.TypeHint.Builder; import org.springframework.lang.Nullable; +import org.springframework.util.ClassUtils; /** * Gather the need for reflection at runtime. @@ -95,6 +96,22 @@ public class ReflectionHints { return registerType(TypeReference.of(type), typeHint); } + /** + * Register or customize reflection hints for the specified type if it + * is available using the specified {@link ClassLoader}. + * @param classLoader the classloader to use to check if the type is present + * @param typeName the type to customize + * @param typeHint a builder to further customize hints for that type + * @return {@code this}, to facilitate method chaining + */ + public ReflectionHints registerTypeIfPresent(@Nullable ClassLoader classLoader, + String typeName, Consumer typeHint) { + if (ClassUtils.isPresent(typeName, classLoader)) { + registerType(TypeReference.of(typeName), typeHint); + } + return this; + } + /** * Register or customize reflection hints for the types defined by the * specified list of {@link TypeReference type references}. The specified diff --git a/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java b/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java index 2737aacb3f..2a9d14a494 100644 --- a/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java +++ b/spring-core/src/main/java/org/springframework/aot/hint/ResourceHints.java @@ -65,6 +65,24 @@ public class ResourceHints { return this.resourceBundleHints.stream(); } + /** + * Register a pattern if the given {@code location} is available on the + * classpath. This delegates to {@link ClassLoader#getResource(String)} + * which validates directories as well. The location is not included in + * the hint. + * @param classLoader the classloader to use + * @param location a '/'-separated path name that should exist + * @param resourceHint a builder to customize the resource pattern + * @return {@code this}, to facilitate method chaining + */ + public ResourceHints registerPatternIfPresent(@Nullable ClassLoader classLoader, String location, Consumer resourceHint) { + ClassLoader classLoaderToUse = (classLoader != null) ? classLoader : getClass().getClassLoader(); + if (classLoaderToUse.getResource(location) != null) { + registerPattern(resourceHint); + } + return this; + } + /** * Register that the resources matching the specified pattern should be * made available at runtime. diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java index 7288e21fde..f4206130a5 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ReflectionHintsTests.java @@ -27,6 +27,8 @@ import org.springframework.lang.Nullable; import org.springframework.util.ReflectionUtils; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; /** * Tests for {@link ReflectionHints}. @@ -45,6 +47,23 @@ class ReflectionHintsTests { typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS)); } + @Test + void registerTypeIfPresentRegisterExistingClass() { + this.reflectionHints.registerTypeIfPresent(null, String.class.getName(), + hint -> hint.withMembers(MemberCategory.DECLARED_FIELDS)); + assertThat(this.reflectionHints.typeHints()).singleElement().satisfies( + typeWithMemberCategories(String.class, MemberCategory.DECLARED_FIELDS)); + } + + @Test + @SuppressWarnings("unchecked") + void registerTypeIfPresentIgnoreMissingClass() { + Consumer hintBuilder = mock(Consumer.class); + this.reflectionHints.registerTypeIfPresent(null, "com.example.DoesNotExist", hintBuilder); + assertThat(this.reflectionHints.typeHints()).isEmpty(); + verifyNoInteractions(hintBuilder); + } + @Test void getTypeUsingType() { this.reflectionHints.registerType(TypeReference.of(String.class), diff --git a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java index b9936c8197..209e72cece 100644 --- a/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java +++ b/spring-core/src/test/java/org/springframework/aot/hint/ResourceHintsTests.java @@ -26,6 +26,8 @@ import org.junit.jupiter.api.Test; import org.springframework.aot.hint.ResourceHintsTests.Nested.Inner; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verifyNoInteractions; /** * Tests for {@link ResourceHints}. @@ -91,6 +93,23 @@ class ResourceHintsTests { List.of("com/example/to-ignore.properties"))); } + @Test + void registerIfPresentRegisterExistingLocation() { + this.resourceHints.registerPatternIfPresent(null, "META-INF/", + resourceHint -> resourceHint.includes("com/example/*.properties")); + assertThat(this.resourceHints.resourcePatterns()).singleElement().satisfies( + patternOf("com/example/*.properties")); + } + + @Test + @SuppressWarnings("unchecked") + void registerIfPresentIgnoreMissingLocation() { + Consumer hintBuilder = mock(Consumer.class); + this.resourceHints.registerPatternIfPresent(null, "location/does-not-exist/", hintBuilder); + assertThat(this.resourceHints.resourcePatterns()).isEmpty(); + verifyNoInteractions(hintBuilder); + } + @Test void registerResourceBundle() { this.resourceHints.registerResourceBundle("com.example.message");