From 138c55ee117891a12d31910925fabc587e611d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 29 Aug 2022 16:46:27 +0200 Subject: [PATCH] Refine ConfigurationPropertiesReflectionHintsProcessor This commit refines ConfigurationPropertiesReflectionHintsProcessor Java bean properties handling in order to register reflection hints only for getters and setters, not for all methods. It avoids including unconditionally method like SpringApplication#load which in turn avoids shipping BeanDefinitionLoader and related transitively used classes in the native image. The gain is significant: it allows to remove up to 700 classes (when no XML parser is used elsewhere) and to reduce the memory footprint by 2M of RSS. Closes gh-32186 --- ...ssageConvertersAutoConfigurationTests.java | 11 ++++--- ...ionPropertiesReflectionHintsProcessor.java | 12 ++++--- .../boot/SpringApplicationTests.java | 12 ++++--- ...actoryInitializationAotProcessorTests.java | 32 +++++++------------ 4 files changed, 34 insertions(+), 33 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java index 2293c90a79..6b65831cd2 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/http/HttpMessageConvertersAutoConfigurationTests.java @@ -23,7 +23,6 @@ import com.google.gson.Gson; import jakarta.json.bind.Jsonb; import org.junit.jupiter.api.Test; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.beans.factory.config.BeanDefinition; @@ -67,6 +66,7 @@ import static org.assertj.core.api.Assertions.assertThat; * @author Sebastien Deleuze * @author EddĂș MelĂ©ndez * @author Moritz Halbritter + * @author Sebastien Deleuze */ class HttpMessageConvertersAutoConfigurationTests { @@ -281,9 +281,12 @@ class HttpMessageConvertersAutoConfigurationTests { void shouldRegisterHints() { RuntimeHints hints = new RuntimeHints(); new HttpMessageConvertersAutoConfigurationRuntimeHints().registerHints(hints, getClass().getClassLoader()); - assertThat(RuntimeHintsPredicates.reflection().onType(Encoding.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(Encoding.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(Encoding.class, "getCharset")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(Encoding.class, "setCharset")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(Encoding.class, "isForce")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(Encoding.class, "setForce")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(Encoding.class, "shouldForce")).rejects(hints); } private ApplicationContextRunner allOptionsRunner() { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java index a29e83cdb9..507647224f 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesReflectionHintsProcessor.java @@ -29,7 +29,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.springframework.aot.hint.MemberCategory; +import org.springframework.aot.hint.ExecutableMode; import org.springframework.aot.hint.ReflectionHints; import org.springframework.beans.BeanInfoFactory; import org.springframework.beans.ExtendedBeanInfoFactory; @@ -44,6 +44,7 @@ import org.springframework.util.ReflectionUtils; * * @author Andy Wilkinson * @author Moritz Halbritter + * @author Sebastien Deleuze * @since 3.0.0 */ public final class ConfigurationPropertiesReflectionHintsProcessor { @@ -95,8 +96,6 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { return; } this.seen.add(this.type); - reflectionHints.registerType(this.type, (hint) -> hint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS)); handleConstructor(reflectionHints); if (this.bindConstructor != null) { handleValueObjectProperties(reflectionHints); @@ -129,14 +128,19 @@ public final class ConfigurationPropertiesReflectionHintsProcessor { private void handleJavaBeanProperties(ReflectionHints reflectionHints) { for (PropertyDescriptor propertyDescriptor : this.beanInfo.getPropertyDescriptors()) { + Method writeMethod = propertyDescriptor.getWriteMethod(); + if (writeMethod != null) { + reflectionHints.registerMethod(writeMethod, ExecutableMode.INVOKE); + } Method readMethod = propertyDescriptor.getReadMethod(); if (readMethod != null) { ResolvableType propertyType = ResolvableType.forMethodReturnType(readMethod, this.type); String propertyName = propertyDescriptor.getName(); - if (isSetterMandatory(propertyName, propertyType) && propertyDescriptor.getWriteMethod() == null) { + if (isSetterMandatory(propertyName, propertyType) && writeMethod == null) { continue; } handleProperty(reflectionHints, propertyName, propertyType); + reflectionHints.registerMethod(readMethod, ExecutableMode.INVOKE); } } } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java index 5326bddcb4..eabbf1164f 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationTests.java @@ -41,7 +41,6 @@ import org.mockito.InOrder; import org.mockito.Mockito; import reactor.core.publisher.Mono; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.predicate.RuntimeHintsPredicates; import org.springframework.beans.CachedIntrospectionResults; @@ -158,7 +157,7 @@ import static org.mockito.Mockito.spy; * @author Marten Deinum * @author Nguyen Bao Sach * @author Chris Bono - * @author Brian Clozel + * @author Sebastien Deleuze */ @ExtendWith(OutputCaptureExtension.class) class SpringApplicationTests { @@ -1309,9 +1308,12 @@ class SpringApplicationTests { void shouldRegisterHints() { RuntimeHints hints = new RuntimeHints(); new SpringApplicationRuntimeHints().registerHints(hints, getClass().getClassLoader()); - assertThat(RuntimeHintsPredicates.reflection().onType(SpringApplication.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onType(SpringApplication.class)).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SpringApplication.class, "setBannerMode")) + .accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SpringApplication.class, "getSources")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SpringApplication.class, "setSources")).accepts(hints); + assertThat(RuntimeHintsPredicates.reflection().onMethod(SpringApplication.class, "load")).rejects(hints); } private ArgumentMatcher isAvailabilityChangeEventWithState( diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java index 4bb0d996c3..a3b1653db9 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java @@ -31,7 +31,6 @@ import org.springframework.aot.generate.DefaultGenerationContext; import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.generate.InMemoryGeneratedFiles; import org.springframework.aot.hint.ExecutableHint; -import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; import org.springframework.aot.hint.TypeHint; import org.springframework.aot.hint.TypeReference; @@ -57,6 +56,7 @@ import static org.mockito.Mockito.mock; * * @author Stephane Nicoll * @author Moritz Halbritter + * @author Sebastien Deleuze */ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { @@ -233,26 +233,17 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { @Test void processConfigurationPropertiesWithNestedGenerics() { RuntimeHints runtimeHints = process(NestedGenerics.class); - assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.class)).accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(NestedGenerics.Nested.class)).accepts(runtimeHints); } @Test void processConfigurationPropertiesWithMultipleNestedClasses() { RuntimeHints runtimeHints = process(TripleNested.class); - assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(runtimeHints); - assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class) - .withMemberCategories(MemberCategory.INVOKE_DECLARED_METHODS, MemberCategory.INVOKE_PUBLIC_METHODS)) - .accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.class)).accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.class)).accepts(runtimeHints); + assertThat(RuntimeHintsPredicates.reflection().onType(TripleNested.DoubleNested.Nested.class)) + .accepts(runtimeHints); } private Consumer javaBeanBinding(Class type) { @@ -263,8 +254,9 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { return (entry) -> { assertThat(entry.getType()).isEqualTo(TypeReference.of(type)); assertThat(entry.constructors()).singleElement().satisfies(match(constructor)); - assertThat(entry.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS); + assertThat(entry.getMemberCategories()).isEmpty(); + assertThat(entry.methods()).allMatch((t) -> t.getName().startsWith("set") || t.getName().startsWith("get") + || t.getName().startsWith("is")); }; } @@ -272,8 +264,8 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { return (entry) -> { assertThat(entry.getType()).isEqualTo(TypeReference.of(type)); assertThat(entry.constructors()).singleElement().satisfies(match(constructor)); - assertThat(entry.getMemberCategories()).containsOnly(MemberCategory.INVOKE_DECLARED_METHODS, - MemberCategory.INVOKE_PUBLIC_METHODS); + assertThat(entry.getMemberCategories()).isEmpty(); + assertThat(entry.methods()).isEmpty(); }; }