From 3f0c14187adde3b044b5dc09d2fc26e05bd23fdb Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 7 Jun 2022 09:50:56 +0200 Subject: [PATCH] Register runtime hints for @ConfigurationProperties Prior to this commit, the `@ConfigurationProperties` annotation would not be registered for reflection hints: this means it could be missing at runtime in a native image and would not be registered for JDK proxying - this can fail the synthesized annotation resolution. This commit ensures that hints are registered for this annotation if configuration properties are declared in the bean factory. Fixes gh-31227 --- ...BeanFactoryInitializationAotProcessor.java | 2 + ...actoryInitializationAotProcessorTests.java | 39 +++++++++++-------- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java index 50ecd2e9d4fa..d8603947e193 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java @@ -34,6 +34,7 @@ import org.springframework.aot.generate.GenerationContext; import org.springframework.aot.hint.MemberCategory; import org.springframework.aot.hint.RuntimeHints; +import org.springframework.aot.hint.support.RuntimeHintsUtils; import org.springframework.beans.BeanInfoFactory; import org.springframework.beans.ExtendedBeanInfoFactory; import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution; @@ -86,6 +87,7 @@ private ConfigurationPropertiesReflectionHintsContribution(Iterable> ty @Override public void applyTo(GenerationContext generationContext, BeanFactoryInitializationCode beanFactoryInitializationCode) { + RuntimeHintsUtils.registerAnnotation(generationContext.getRuntimeHints(), ConfigurationProperties.class); for (Class type : this.types) { TypeProcessor.processConfigurationProperties(type, generationContext.getRuntimeHints()); } 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 918f3ced35e8..29c689f6f5f3 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 @@ -76,21 +76,28 @@ void processManuallyRegisteredSingleton() { DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(); beanFactory.registerSingleton("test", new SampleProperties()); RuntimeHints runtimeHints = process(beanFactory); - assertThat(runtimeHints.reflection().typeHints()).singleElement() + assertThat(runtimeHints.reflection().getTypeHint(SampleProperties.class)) .satisfies(javaBeanBinding(SampleProperties.class)); } + @Test + void registerConfigurationPropertiesAnnotation() { + RuntimeHints runtimeHints = process(SampleProperties.class); + assertThat(runtimeHints.reflection().getTypeHint(ConfigurationProperties.class)).satisfies( + (hint) -> assertThat(hint.getMemberCategories()).contains(MemberCategory.INVOKE_DECLARED_METHODS)); + } + @Test void processJavaBeanConfigurationProperties() { RuntimeHints runtimeHints = process(SampleProperties.class); - assertThat(runtimeHints.reflection().typeHints()).singleElement() + assertThat(runtimeHints.reflection().getTypeHint(SampleProperties.class)) .satisfies(javaBeanBinding(SampleProperties.class)); } @Test void processJavaBeanConfigurationPropertiesWithSeveralConstructors() throws NoSuchMethodException { RuntimeHints runtimeHints = process(SamplePropertiesWithSeveralConstructors.class); - assertThat(runtimeHints.reflection().typeHints()).singleElement() + assertThat(runtimeHints.reflection().getTypeHint(SamplePropertiesWithSeveralConstructors.class)) .satisfies(javaBeanBinding(SamplePropertiesWithSeveralConstructors.class, SamplePropertiesWithSeveralConstructors.class.getDeclaredConstructor())); } @@ -101,7 +108,7 @@ void processJavaBeanConfigurationPropertiesWithMapOfPojo() { List typeHints = runtimeHints.reflection().typeHints().toList(); assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithMap.class)); assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); - assertThat(typeHints).hasSize(2); + assertThat(typeHints).hasSize(3); } @Test @@ -110,7 +117,7 @@ void processJavaBeanConfigurationPropertiesWithListOfPojo() { List typeHints = runtimeHints.reflection().typeHints().toList(); assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithList.class)); assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); - assertThat(typeHints).hasSize(2); + assertThat(typeHints).hasSize(3); } @Test @@ -119,7 +126,7 @@ void processJavaBeanConfigurationPropertiesWitArrayOfPojo() { List typeHints = runtimeHints.reflection().typeHints().toList(); assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithArray.class)); assertThat(typeHints).anySatisfy(javaBeanBinding(Address.class)); - assertThat(typeHints).hasSize(2); + assertThat(typeHints).hasSize(3); } @Test @@ -127,7 +134,7 @@ void processJavaBeanConfigurationPropertiesWithListOfJavaType() { RuntimeHints runtimeHints = process(SamplePropertiesWithSimpleList.class); List typeHints = runtimeHints.reflection().typeHints().toList(); assertThat(typeHints).anySatisfy(javaBeanBinding(SamplePropertiesWithSimpleList.class)); - assertThat(typeHints).hasSize(1); + assertThat(typeHints).hasSize(2); } @Test @@ -136,7 +143,7 @@ void processValueObjectConfigurationProperties() { List typeHints = runtimeHints.reflection().typeHints().toList(); assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutableProperties.class, SampleImmutableProperties.class.getDeclaredConstructors()[0])); - assertThat(typeHints).hasSize(1); + assertThat(typeHints).hasSize(2); } @Test @@ -145,7 +152,7 @@ void processValueObjectConfigurationPropertiesWithSpecificConstructor() throws N List typeHints = runtimeHints.reflection().typeHints().toList(); assertThat(typeHints).anySatisfy(valueObjectBinding(SampleImmutablePropertiesWithSeveralConstructors.class, SampleImmutablePropertiesWithSeveralConstructors.class.getDeclaredConstructor(String.class))); - assertThat(typeHints).hasSize(1); + assertThat(typeHints).hasSize(2); } @Test @@ -156,13 +163,13 @@ void processValueObjectConfigurationPropertiesWithSeveralLayersOfPojo() { SampleImmutablePropertiesWithList.class.getDeclaredConstructors()[0])); assertThat(typeHints).anySatisfy(valueObjectBinding(Person.class, Person.class.getDeclaredConstructors()[0])); assertThat(typeHints).anySatisfy(valueObjectBinding(Address.class, Address.class.getDeclaredConstructors()[0])); - assertThat(typeHints).hasSize(3); + assertThat(typeHints).hasSize(4); } @Test void processConfigurationPropertiesWithNestedTypeNotUsedIsIgnored() { RuntimeHints runtimeHints = process(SamplePropertiesWithNested.class); - assertThat(runtimeHints.reflection().typeHints()).singleElement() + assertThat(runtimeHints.reflection().getTypeHint(SamplePropertiesWithNested.class)) .satisfies(javaBeanBinding(SamplePropertiesWithNested.class)); } @@ -172,7 +179,7 @@ void processConfigurationPropertiesWithNestedExternalType() { assertThat(runtimeHints.reflection().typeHints()) .anySatisfy(javaBeanBinding(SamplePropertiesWithExternalNested.class)) .anySatisfy(javaBeanBinding(SampleType.class)).anySatisfy(javaBeanBinding(SampleType.Nested.class)) - .hasSize(3); + .hasSize(4); } @Test @@ -180,7 +187,7 @@ void processConfigurationPropertiesWithRecursiveType() { RuntimeHints runtimeHints = process(SamplePropertiesWithRecursive.class); assertThat(runtimeHints.reflection().typeHints()) .anySatisfy(javaBeanBinding(SamplePropertiesWithRecursive.class)) - .anySatisfy(javaBeanBinding(Recursive.class)).hasSize(2); + .anySatisfy(javaBeanBinding(Recursive.class)).hasSize(3); } @Test @@ -191,7 +198,7 @@ void processValueObjectConfigurationPropertiesWithRecursiveType() { SampleImmutablePropertiesWithRecursive.class.getDeclaredConstructors()[0])) .anySatisfy(valueObjectBinding(ImmutableRecursive.class, ImmutableRecursive.class.getDeclaredConstructors()[0])) - .hasSize(2); + .hasSize(3); } @Test @@ -200,7 +207,7 @@ void processConfigurationPropertiesWithWellKnownTypes() { assertThat(runtimeHints.reflection().typeHints()) .anySatisfy(javaBeanBinding(SamplePropertiesWithWellKnownTypes.class)) // TODO - .hasSize(1); + .hasSize(2); } @Test @@ -209,7 +216,7 @@ void processConfigurationPropertiesWithCrossReference() { assertThat(runtimeHints.reflection().typeHints()) .anySatisfy(javaBeanBinding(SamplePropertiesWithCrossReference.class)) .anySatisfy(javaBeanBinding(CrossReferenceA.class)).anySatisfy(javaBeanBinding(CrossReferenceB.class)) - .hasSize(3); + .hasSize(4); } @Test