From dcd733aba8d7b1fe37ed69a0cad852bbc702c7d5 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. --- ...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 2293c90a7936..6b65831cd2fe 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 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 @@ * @author Sebastien Deleuze * @author EddĂș MelĂ©ndez * @author Moritz Halbritter + * @author Sebastien Deleuze */ class HttpMessageConvertersAutoConfigurationTests { @@ -281,9 +281,12 @@ void whenAutoConfigurationIsActiveThenServerPropertiesConfigurationPropertiesAre 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 a29e83cdb959..507647224f75 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.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 @@ * * @author Andy Wilkinson * @author Moritz Halbritter + * @author Sebastien Deleuze * @since 3.0.0 */ public final class ConfigurationPropertiesReflectionHintsProcessor { @@ -95,8 +96,6 @@ private void process(ReflectionHints reflectionHints) { 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 @@ private void handleValueObjectProperties(ReflectionHints reflectionHints) { 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 5326bddcb4b0..eabbf1164f0f 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.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 @@ * @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 @@ void hookIsCalledAndCanPreventRefreshWhenApplicationIsRun() throws Exception { 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 4bb0d996c387..a3b1653db9c1 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.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 @@ * * @author Stephane Nicoll * @author Moritz Halbritter + * @author Sebastien Deleuze */ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests { @@ -233,26 +233,17 @@ void processConfigurationPropertiesWithUnresolvedGeneric() { @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 @@ private Consumer javaBeanBinding(Class type, Constructor constru 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 @@ private Consumer valueObjectBinding(Class type, Constructor cons 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(); }; }