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
This commit is contained in:
Sébastien Deleuze 2022-08-29 16:46:27 +02:00 committed by Brian Clozel
parent 49d3bf1d7f
commit 138c55ee11
4 changed files with 34 additions and 33 deletions

View File

@ -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() {

View File

@ -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);
}
}
}

View File

@ -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 <S extends AvailabilityState> ArgumentMatcher<ApplicationEvent> isAvailabilityChangeEventWithState(

View File

@ -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<TypeHint> 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();
};
}