Register bean reflection hint for property fields

Prior to this commit, the bean definition properties code generator
would register hints for invoking the setter methods of registered
property values defined for the bean definition.
The internal algorithm is also reflecting on the Field to discover
annotations. Doing so actually calls `getDeclaredFields` to iterate on
the available fields. This is done recursively up the type hierarchy
until the field is found.

This commit registers the required reflection metadata.

Closes gh-31390
This commit is contained in:
Brian Clozel 2023-10-09 20:03:06 +02:00
parent 30a94b041f
commit 8064659136
2 changed files with 35 additions and 0 deletions

View File

@ -34,6 +34,7 @@ import java.util.function.Predicate;
import org.springframework.aot.generate.GeneratedMethods;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import org.springframework.beans.BeanUtils;
@ -195,6 +196,13 @@ class BeanDefinitionPropertiesCodeGenerator {
Method writeMethod = writeMethods.get(propertyValue.getName());
if (writeMethod != null) {
this.hints.reflection().registerMethod(writeMethod, ExecutableMode.INVOKE);
// ReflectionUtils#findField searches recursively in the type hierarchy
Class<?> searchType = beanDefinition.getTargetType();
while (searchType != null && searchType != writeMethod.getDeclaringClass()) {
this.hints.reflection().registerType(searchType, MemberCategory.DECLARED_FIELDS);
searchType = searchType.getSuperclass();
}
this.hints.reflection().registerType(writeMethod.getDeclaringClass(), MemberCategory.DECLARED_FIELDS);
}
}
}

View File

@ -34,6 +34,7 @@ import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.beans.factory.FactoryBean;
@ -240,6 +241,21 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
});
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
assertHasDecalredFieldsHint(PropertyValuesBean.class);
}
@Test
void propertyValuesWhenValuesOnParentClass() {
this.beanDefinition.setTargetType(ExtendedPropertyValuesBean.class);
this.beanDefinition.getPropertyValues().add("test", String.class);
this.beanDefinition.getPropertyValues().add("spring", "framework");
compile((actual, compiled) -> {
assertThat(actual.getPropertyValues().get("test")).isEqualTo(String.class);
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
});
assertHasMethodInvokeHints(PropertyValuesBean.class, "setTest", "setSpring");
assertHasDecalredFieldsHint(ExtendedPropertyValuesBean.class);
assertHasDecalredFieldsHint(PropertyValuesBean.class);
}
@Test
@ -300,6 +316,7 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
assertThat(actual.getPropertyValues().get("name")).isEqualTo("World");
});
assertHasMethodInvokeHints(PropertyValuesFactoryBean.class, "setPrefix", "setName" );
assertHasDecalredFieldsHint(PropertyValuesFactoryBean.class);
}
@Test
@ -453,6 +470,12 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
.test(this.generationContext.getRuntimeHints()));
}
private void assertHasDecalredFieldsHint(Class<?> beanType) {
assertThat(RuntimeHintsPredicates.reflection()
.onType(beanType).withMemberCategory(MemberCategory.DECLARED_FIELDS))
.accepts(this.generationContext.getRuntimeHints());
}
private void compile(BiConsumer<RootBeanDefinition, Compiled> result) {
compile(attribute -> true, result);
}
@ -524,6 +547,10 @@ class BeanDefinitionPropertiesCodeGeneratorTests {
}
static class ExtendedPropertyValuesBean extends PropertyValuesBean {
}
static class PropertyValuesFactoryBean implements FactoryBean<String> {
private String prefix;