Skip to content

Commit 3df3bc2

Browse files
committed
Generate native hints for property values
Update `BeanDefinitionPropertiesCodeGenerator` so that hints are generated for property values. This restores functionality that was inadvertently removed during refactoring. See gh-28414
1 parent 91441ba commit 3df3bc2

File tree

2 files changed

+108
-18
lines changed

2 files changed

+108
-18
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGenerator.java

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,28 @@
1616

1717
package org.springframework.beans.factory.aot;
1818

19+
import java.beans.BeanInfo;
20+
import java.beans.IntrospectionException;
21+
import java.beans.Introspector;
22+
import java.beans.PropertyDescriptor;
1923
import java.lang.reflect.Method;
24+
import java.util.Arrays;
25+
import java.util.Collections;
26+
import java.util.HashMap;
2027
import java.util.Map;
2128
import java.util.Objects;
2229
import java.util.function.BiFunction;
2330
import java.util.function.BiPredicate;
31+
import java.util.function.Consumer;
2432
import java.util.function.Function;
2533
import java.util.function.Predicate;
2634

2735
import org.springframework.aot.generate.MethodGenerator;
36+
import org.springframework.aot.hint.ExecutableHint;
37+
import org.springframework.aot.hint.ExecutableMode;
2838
import org.springframework.aot.hint.RuntimeHints;
39+
import org.springframework.beans.BeanInfoFactory;
40+
import org.springframework.beans.ExtendedBeanInfoFactory;
2941
import org.springframework.beans.MutablePropertyValues;
3042
import org.springframework.beans.PropertyValue;
3143
import org.springframework.beans.factory.config.BeanDefinition;
@@ -36,6 +48,7 @@
3648
import org.springframework.beans.factory.support.RootBeanDefinition;
3749
import org.springframework.javapoet.CodeBlock;
3850
import org.springframework.javapoet.CodeBlock.Builder;
51+
import org.springframework.lang.Nullable;
3952
import org.springframework.util.ClassUtils;
4053
import org.springframework.util.ObjectUtils;
4154
import org.springframework.util.ReflectionUtils;
@@ -69,6 +82,9 @@ class BeanDefinitionPropertiesCodeGenerator {
6982

7083
private static final String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE;
7184

85+
private static final Consumer<ExecutableHint.Builder> INVOKE_HINT = hint -> hint.withMode(ExecutableMode.INVOKE);
86+
87+
private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory();
7288

7389
private final RuntimeHints hints;
7490

@@ -125,14 +141,14 @@ private void addInitDestroyMethods(Builder builder,
125141
AbstractBeanDefinition beanDefinition, String[] methodNames, String format) {
126142

127143
if (!ObjectUtils.isEmpty(methodNames)) {
128-
Class<?> beanUserClass = ClassUtils
144+
Class<?> beanType = ClassUtils
129145
.getUserClass(beanDefinition.getResolvableType().toClass());
130146
Builder arguments = CodeBlock.builder();
131147
for (int i = 0; i < methodNames.length; i++) {
132148
String methodName = methodNames[i];
133149
if (!AbstractBeanDefinition.INFER_METHOD.equals(methodName)) {
134150
arguments.add((i != 0) ? ", $S" : "$S", methodName);
135-
addInitDestroyHint(beanUserClass, methodName);
151+
addInitDestroyHint(beanType, methodName);
136152
}
137153
}
138154
builder.addStatement(format, BEAN_DEFINITION_VARIABLE, arguments.build());
@@ -181,9 +197,53 @@ private void addPropertyValues(CodeBlock.Builder builder,
181197
builder.addStatement("$L.getPropertyValues().addPropertyValue($S, $L)",
182198
BEAN_DEFINITION_VARIABLE, propertyValue.getName(), code);
183199
}
200+
Class<?> beanType = ClassUtils
201+
.getUserClass(beanDefinition.getResolvableType().toClass());
202+
BeanInfo beanInfo = (beanType != Object.class) ? getBeanInfo(beanType) : null;
203+
if (beanInfo != null) {
204+
Map<String, Method> writeMethods = getWriteMethods(beanInfo);
205+
for (PropertyValue propertyValue : propertyValues) {
206+
Method writeMethod = writeMethods.get(propertyValue.getName());
207+
if (writeMethod != null) {
208+
this.hints.reflection().registerMethod(writeMethod, INVOKE_HINT);
209+
}
210+
}
211+
}
184212
}
185213
}
186214

215+
@Nullable
216+
private BeanInfo getBeanInfo(Class<?> beanType) {
217+
try {
218+
BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType);
219+
if (beanInfo != null) {
220+
return beanInfo;
221+
}
222+
return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO);
223+
}
224+
catch (IntrospectionException ex) {
225+
return null;
226+
}
227+
}
228+
229+
private Map<String, Method> getWriteMethods(BeanInfo beanInfo) {
230+
Map<String, Method> writeMethods = new HashMap<>();
231+
for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
232+
writeMethods.put(propertyDescriptor.getName(),
233+
propertyDescriptor.getWriteMethod());
234+
}
235+
return Collections.unmodifiableMap(writeMethods);
236+
}
237+
238+
@Nullable
239+
private Method findWriteMethod(BeanInfo beanInfo, String propertyName) {
240+
return Arrays.stream(beanInfo.getPropertyDescriptors())
241+
.filter(pd -> propertyName.equals(pd.getName()))
242+
.map(java.beans.PropertyDescriptor::getWriteMethod)
243+
.filter(Objects::nonNull).findFirst().orElse(null);
244+
}
245+
246+
187247
private void addAttributes(CodeBlock.Builder builder, BeanDefinition beanDefinition) {
188248
String[] attributeNames = beanDefinition.attributeNames();
189249
if (!ObjectUtils.isEmpty(attributeNames)) {

spring-beans/src/test/java/org/springframework/beans/factory/aot/BeanDefinitionPropertiesCodeGeneratorTests.java

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ void setInitMethodWhenSingleInitMethod() {
225225
this.beanDefinition.setInitMethodName("i1");
226226
testCompiledResult((actual, compiled) -> assertThat(actual.getInitMethodNames())
227227
.containsExactly("i1"));
228-
assertHasMethodInvokeHints("i1");
228+
String[] methodNames = { "i1" };
229+
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
229230
}
230231

231232
@Test
@@ -234,7 +235,8 @@ void setInitMethodWhenMultipleInitMethods() {
234235
this.beanDefinition.setInitMethodNames("i1", "i2");
235236
testCompiledResult((actual, compiled) -> assertThat(actual.getInitMethodNames())
236237
.containsExactly("i1", "i2"));
237-
assertHasMethodInvokeHints("i1", "i2");
238+
String[] methodNames = { "i1", "i2" };
239+
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
238240
}
239241

240242
@Test
@@ -244,7 +246,8 @@ void setDestroyMethodWhenDestroyInitMethod() {
244246
testCompiledResult(
245247
(actual, compiled) -> assertThat(actual.getDestroyMethodNames())
246248
.containsExactly("d1"));
247-
assertHasMethodInvokeHints("d1");
249+
String[] methodNames = { "d1" };
250+
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
248251
}
249252

250253
@Test
@@ -254,20 +257,20 @@ void setDestroyMethodWhenMultipleDestroyMethods() {
254257
testCompiledResult(
255258
(actual, compiled) -> assertThat(actual.getDestroyMethodNames())
256259
.containsExactly("d1", "d2"));
257-
assertHasMethodInvokeHints("d1", "d2");
258-
}
259-
260-
private void assertHasMethodInvokeHints(String... methodNames) {
261-
assertThat(hints.reflection().getTypeHint(InitDestroyBean.class))
262-
.satisfies(typeHint -> {
263-
for (String methodName : methodNames) {
264-
assertThat(typeHint.methods()).anySatisfy(methodHint -> {
265-
assertThat(methodHint.getName()).isEqualTo(methodName);
266-
assertThat(methodHint.getModes())
267-
.containsExactly(ExecutableMode.INVOKE);
268-
});
269-
}
260+
String[] methodNames = { "d1", "d2" };
261+
assertHasMethodInvokeHints(InitDestroyBean.class, methodNames);
262+
}
263+
264+
private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) {
265+
assertThat(this.hints.reflection().getTypeHint(beanType)).satisfies(typeHint -> {
266+
for (String methodName : methodNames) {
267+
assertThat(typeHint.methods()).anySatisfy(methodHint -> {
268+
assertThat(methodHint.getName()).isEqualTo(methodName);
269+
assertThat(methodHint.getModes())
270+
.containsExactly(ExecutableMode.INVOKE);
270271
});
272+
}
273+
});
271274
}
272275

273276
@Test
@@ -289,12 +292,15 @@ void constructorArgumentValuesWhenValues() {
289292

290293
@Test
291294
void propertyValuesWhenValues() {
295+
this.beanDefinition.setTargetType(PropertyValuesBean.class);
292296
this.beanDefinition.getPropertyValues().add("test", String.class);
293297
this.beanDefinition.getPropertyValues().add("spring", "framework");
294298
testCompiledResult((actual, compiled) -> {
295299
assertThat(actual.getPropertyValues().get("test")).isEqualTo(String.class);
296300
assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework");
297301
});
302+
String[] methodNames = { "setTest", "setSpring" };
303+
assertHasMethodInvokeHints(PropertyValuesBean.class, methodNames);
298304
}
299305

300306
@Test
@@ -438,4 +444,28 @@ void d2() {
438444

439445
}
440446

447+
static class PropertyValuesBean {
448+
449+
private Class<?> test;
450+
451+
private String spring;
452+
453+
public Class<?> getTest() {
454+
return this.test;
455+
}
456+
457+
public void setTest(Class<?> test) {
458+
this.test = test;
459+
}
460+
461+
public String getSpring() {
462+
return this.spring;
463+
}
464+
465+
public void setSpring(String spring) {
466+
this.spring = spring;
467+
}
468+
469+
}
470+
441471
}

0 commit comments

Comments
 (0)