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
This commit is contained in:
		
							parent
							
								
									91441ba653
								
							
						
					
					
						commit
						3df3bc2aa0
					
				|  | @ -16,16 +16,28 @@ | |||
| 
 | ||||
| package org.springframework.beans.factory.aot; | ||||
| 
 | ||||
| import java.beans.BeanInfo; | ||||
| import java.beans.IntrospectionException; | ||||
| import java.beans.Introspector; | ||||
| import java.beans.PropertyDescriptor; | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| import java.util.HashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Objects; | ||||
| import java.util.function.BiFunction; | ||||
| import java.util.function.BiPredicate; | ||||
| import java.util.function.Consumer; | ||||
| import java.util.function.Function; | ||||
| import java.util.function.Predicate; | ||||
| 
 | ||||
| import org.springframework.aot.generate.MethodGenerator; | ||||
| import org.springframework.aot.hint.ExecutableHint; | ||||
| import org.springframework.aot.hint.ExecutableMode; | ||||
| import org.springframework.aot.hint.RuntimeHints; | ||||
| import org.springframework.beans.BeanInfoFactory; | ||||
| import org.springframework.beans.ExtendedBeanInfoFactory; | ||||
| import org.springframework.beans.MutablePropertyValues; | ||||
| import org.springframework.beans.PropertyValue; | ||||
| import org.springframework.beans.factory.config.BeanDefinition; | ||||
|  | @ -36,6 +48,7 @@ import org.springframework.beans.factory.support.InstanceSupplier; | |||
| import org.springframework.beans.factory.support.RootBeanDefinition; | ||||
| import org.springframework.javapoet.CodeBlock; | ||||
| import org.springframework.javapoet.CodeBlock.Builder; | ||||
| import org.springframework.lang.Nullable; | ||||
| import org.springframework.util.ClassUtils; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| import org.springframework.util.ReflectionUtils; | ||||
|  | @ -69,6 +82,9 @@ class BeanDefinitionPropertiesCodeGenerator { | |||
| 
 | ||||
| 	private static final String BEAN_DEFINITION_VARIABLE = BeanRegistrationCodeFragments.BEAN_DEFINITION_VARIABLE; | ||||
| 
 | ||||
| 	private static final	Consumer<ExecutableHint.Builder> INVOKE_HINT = hint -> hint.withMode(ExecutableMode.INVOKE); | ||||
| 
 | ||||
| 	private static final BeanInfoFactory beanInfoFactory = new ExtendedBeanInfoFactory(); | ||||
| 
 | ||||
| 	private final RuntimeHints hints; | ||||
| 
 | ||||
|  | @ -125,14 +141,14 @@ class BeanDefinitionPropertiesCodeGenerator { | |||
| 			AbstractBeanDefinition beanDefinition, String[] methodNames, String format) { | ||||
| 
 | ||||
| 		if (!ObjectUtils.isEmpty(methodNames)) { | ||||
| 			Class<?> beanUserClass = ClassUtils | ||||
| 			Class<?> beanType = ClassUtils | ||||
| 					.getUserClass(beanDefinition.getResolvableType().toClass()); | ||||
| 			Builder arguments = CodeBlock.builder(); | ||||
| 			for (int i = 0; i < methodNames.length; i++) { | ||||
| 				String methodName = methodNames[i]; | ||||
| 				if (!AbstractBeanDefinition.INFER_METHOD.equals(methodName)) { | ||||
| 					arguments.add((i != 0) ? ", $S" : "$S", methodName); | ||||
| 					addInitDestroyHint(beanUserClass, methodName); | ||||
| 					addInitDestroyHint(beanType, methodName); | ||||
| 				} | ||||
| 			} | ||||
| 			builder.addStatement(format, BEAN_DEFINITION_VARIABLE, arguments.build()); | ||||
|  | @ -181,8 +197,52 @@ class BeanDefinitionPropertiesCodeGenerator { | |||
| 				builder.addStatement("$L.getPropertyValues().addPropertyValue($S, $L)", | ||||
| 						BEAN_DEFINITION_VARIABLE, propertyValue.getName(), code); | ||||
| 			} | ||||
| 			Class<?> beanType = ClassUtils | ||||
| 					.getUserClass(beanDefinition.getResolvableType().toClass()); | ||||
| 			BeanInfo beanInfo = (beanType != Object.class) ? getBeanInfo(beanType) : null; | ||||
| 			if (beanInfo != null) { | ||||
| 				Map<String, Method> writeMethods = getWriteMethods(beanInfo); | ||||
| 				for (PropertyValue propertyValue : propertyValues) { | ||||
| 					Method writeMethod = writeMethods.get(propertyValue.getName()); | ||||
| 					if (writeMethod != null) { | ||||
| 						this.hints.reflection().registerMethod(writeMethod, INVOKE_HINT); | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Nullable | ||||
| 	private BeanInfo getBeanInfo(Class<?> beanType) { | ||||
| 		try { | ||||
| 			BeanInfo beanInfo = beanInfoFactory.getBeanInfo(beanType); | ||||
| 			if (beanInfo != null) { | ||||
| 				return beanInfo; | ||||
| 			} | ||||
| 			return Introspector.getBeanInfo(beanType, Introspector.IGNORE_ALL_BEANINFO); | ||||
| 		} | ||||
| 		catch (IntrospectionException ex) { | ||||
| 			return null; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private Map<String, Method> getWriteMethods(BeanInfo beanInfo) { | ||||
| 		Map<String, Method> writeMethods = new HashMap<>(); | ||||
| 		for (PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) { | ||||
| 			writeMethods.put(propertyDescriptor.getName(), | ||||
| 					propertyDescriptor.getWriteMethod()); | ||||
| 		} | ||||
| 		return Collections.unmodifiableMap(writeMethods); | ||||
| 	} | ||||
| 
 | ||||
| 	@Nullable | ||||
| 	private Method findWriteMethod(BeanInfo beanInfo, String propertyName) { | ||||
| 		return Arrays.stream(beanInfo.getPropertyDescriptors()) | ||||
| 				.filter(pd -> propertyName.equals(pd.getName())) | ||||
| 				.map(java.beans.PropertyDescriptor::getWriteMethod) | ||||
| 				.filter(Objects::nonNull).findFirst().orElse(null); | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	private void addAttributes(CodeBlock.Builder builder, BeanDefinition beanDefinition) { | ||||
| 		String[] attributeNames = beanDefinition.attributeNames(); | ||||
|  |  | |||
|  | @ -225,7 +225,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests { | |||
| 		this.beanDefinition.setInitMethodName("i1"); | ||||
| 		testCompiledResult((actual, compiled) -> assertThat(actual.getInitMethodNames()) | ||||
| 				.containsExactly("i1")); | ||||
| 		assertHasMethodInvokeHints("i1"); | ||||
| 		String[] methodNames = { "i1" }; | ||||
| 		assertHasMethodInvokeHints(InitDestroyBean.class, methodNames); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
|  | @ -234,7 +235,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests { | |||
| 		this.beanDefinition.setInitMethodNames("i1", "i2"); | ||||
| 		testCompiledResult((actual, compiled) -> assertThat(actual.getInitMethodNames()) | ||||
| 				.containsExactly("i1", "i2")); | ||||
| 		assertHasMethodInvokeHints("i1", "i2"); | ||||
| 		String[] methodNames = { "i1", "i2" }; | ||||
| 		assertHasMethodInvokeHints(InitDestroyBean.class, methodNames); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
|  | @ -244,7 +246,8 @@ class BeanDefinitionPropertiesCodeGeneratorTests { | |||
| 		testCompiledResult( | ||||
| 				(actual, compiled) -> assertThat(actual.getDestroyMethodNames()) | ||||
| 						.containsExactly("d1")); | ||||
| 		assertHasMethodInvokeHints("d1"); | ||||
| 		String[] methodNames = { "d1" }; | ||||
| 		assertHasMethodInvokeHints(InitDestroyBean.class, methodNames); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
|  | @ -254,12 +257,12 @@ class BeanDefinitionPropertiesCodeGeneratorTests { | |||
| 		testCompiledResult( | ||||
| 				(actual, compiled) -> assertThat(actual.getDestroyMethodNames()) | ||||
| 						.containsExactly("d1", "d2")); | ||||
| 		assertHasMethodInvokeHints("d1", "d2"); | ||||
| 		String[] methodNames = { "d1", "d2" }; | ||||
| 		assertHasMethodInvokeHints(InitDestroyBean.class, methodNames); | ||||
| 	} | ||||
| 
 | ||||
| 	private void assertHasMethodInvokeHints(String... methodNames) { | ||||
| 		assertThat(hints.reflection().getTypeHint(InitDestroyBean.class)) | ||||
| 				.satisfies(typeHint -> { | ||||
| 	private void assertHasMethodInvokeHints(Class<?> beanType, String... methodNames) { | ||||
| 		assertThat(this.hints.reflection().getTypeHint(beanType)).satisfies(typeHint -> { | ||||
| 			for (String methodName : methodNames) { | ||||
| 				assertThat(typeHint.methods()).anySatisfy(methodHint -> { | ||||
| 					assertThat(methodHint.getName()).isEqualTo(methodName); | ||||
|  | @ -289,12 +292,15 @@ class BeanDefinitionPropertiesCodeGeneratorTests { | |||
| 
 | ||||
| 	@Test | ||||
| 	void propertyValuesWhenValues() { | ||||
| 		this.beanDefinition.setTargetType(PropertyValuesBean.class); | ||||
| 		this.beanDefinition.getPropertyValues().add("test", String.class); | ||||
| 		this.beanDefinition.getPropertyValues().add("spring", "framework"); | ||||
| 		testCompiledResult((actual, compiled) -> { | ||||
| 			assertThat(actual.getPropertyValues().get("test")).isEqualTo(String.class); | ||||
| 			assertThat(actual.getPropertyValues().get("spring")).isEqualTo("framework"); | ||||
| 		}); | ||||
| 		String[] methodNames = { "setTest", "setSpring" }; | ||||
| 		assertHasMethodInvokeHints(PropertyValuesBean.class, methodNames); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
|  | @ -438,4 +444,28 @@ class BeanDefinitionPropertiesCodeGeneratorTests { | |||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	static class PropertyValuesBean { | ||||
| 
 | ||||
| 		private Class<?> test; | ||||
| 
 | ||||
| 		private String spring; | ||||
| 
 | ||||
| 		public Class<?> getTest() { | ||||
| 			return this.test; | ||||
| 		} | ||||
| 
 | ||||
| 		public void setTest(Class<?> test) { | ||||
| 			this.test = test; | ||||
| 		} | ||||
| 
 | ||||
| 		public String getSpring() { | ||||
| 			return this.spring; | ||||
| 		} | ||||
| 
 | ||||
| 		public void setSpring(String spring) { | ||||
| 			this.spring = spring; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue