@ -16,35 +16,21 @@
@@ -16,35 +16,21 @@
package org.springframework.boot.context.properties ;
import java.lang.reflect.Constructor ;
import java.util.Arrays ;
import java.util.Collections ;
import java.util.HashMap ;
import java.util.List ;
import java.util.Map ;
import java.util.function.Consumer ;
import java.util.stream.Stream ;
import org.junit.jupiter.api.Test ;
import org.springframework.aot.generate.GenerationContext ;
import org.springframework.aot.hint.ExecutableHint ;
import org.springframework.aot.hint.RuntimeHints ;
import org.springframework.aot.hint.TypeHint ;
import org.springframework.aot.hint.TypeReference ;
import org.springframework.aot.hint.predicate.RuntimeHintsPredicates ;
import org.springframework.aot.test.generate.TestGenerationContext ;
import org.springframework.beans.factory.aot.AotServices ;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution ;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor ;
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode ;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory ;
import org.springframework.beans.factory.support.DefaultListableBeanFactory ;
import org.springframework.beans.factory.support.RootBeanDefinition ;
import org.springframework.boot.context.properties.bind.ConstructorBinding ;
import org.springframework.context.ApplicationContext ;
import org.springframework.context.ApplicationContextAware ;
import org.springframework.context.EnvironmentAware ;
import org.springframework.core.env.Environment ;
import org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor.ConfigurationPropertiesReflectionHintsContribution ;
import static org.assertj.core.api.Assertions.assertThat ;
import static org.mockito.Mockito.mock ;
@ -68,209 +54,34 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
@@ -68,209 +54,34 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
@Test
void processNoMatchesReturnsNullContribution ( ) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory ( ) ;
beanFactory . registerBeanDefinition ( "test" , new RootBeanDefinition ( String . class ) ) ;
assertThat ( this . processor . processAheadOfTime ( beanFactory ) ) . isNull ( ) ;
assertThat ( process ( String . class ) ) . isNull ( ) ;
}
@Test
void processManuallyRegisteredSingleton ( ) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory ( ) ;
beanFactory . registerSingleton ( "test" , new SampleProperties ( ) ) ;
RuntimeHints runtimeHints = process ( beanFactory ) ;
assertThat ( runtimeHints . reflection ( ) . getTypeHint ( SampleProperties . class ) )
. satisfies ( javaBeanBinding ( SampleProperties . class ) ) ;
ConfigurationPropertiesReflectionHintsContribution contribution = process ( beanFactory ) ;
assertThat ( contribution . getTypes ( ) ) . containsExactly ( SampleProperties . class ) ;
assertThat ( typeHints ( contribution ) . map ( TypeHint : : getType ) )
. containsExactly ( TypeReference . of ( SampleProperties . class ) ) ;
}
@Test
void processJavaBeanConfigurationProperties ( ) {
RuntimeHints runtimeHints = process ( SampleProperties . class ) ;
assertThat ( runtimeHints . reflection ( ) . getTypeHint ( SampleProperties . class ) )
. satisfies ( javaBeanBinding ( SampleProperties . class ) ) ;
void processDefinedBean ( ) {
ConfigurationPropertiesReflectionHintsContribution contribution = process ( SampleProperties . class ) ;
assertThat ( contribution . getTypes ( ) ) . containsExactly ( SampleProperties . class ) ;
assertThat ( typeHints ( contribution ) . map ( TypeHint : : getType ) )
. containsExactly ( TypeReference . of ( SampleProperties . class ) ) ;
}
@Test
void processJavaBeanConfigurationPropertiesWithSeveralConstructors ( ) throws NoSuchMethodException {
RuntimeHints runtimeHints = process ( SamplePropertiesWithSeveralConstructors . class ) ;
assertThat ( runtimeHints . reflection ( ) . getTypeHint ( SamplePropertiesWithSeveralConstructors . class ) )
. satisfies ( javaBeanBinding ( SamplePropertiesWithSeveralConstructors . class ,
SamplePropertiesWithSeveralConstructors . class . getDeclaredConstructor ( ) ) ) ;
}
@Test
void processJavaBeanConfigurationPropertiesWithMapOfPojo ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithMap . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( SamplePropertiesWithMap . class ) ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( Address . class ) ) ;
assertThat ( typeHints ) . hasSize ( 2 ) ;
}
@Test
void processJavaBeanConfigurationPropertiesWithListOfPojo ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithList . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( SamplePropertiesWithList . class ) ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( Address . class ) ) ;
assertThat ( typeHints ) . hasSize ( 2 ) ;
}
@Test
void processJavaBeanConfigurationPropertiesWitArrayOfPojo ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithArray . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( SamplePropertiesWithArray . class ) ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( Address . class ) ) ;
assertThat ( typeHints ) . hasSize ( 2 ) ;
}
@Test
void processJavaBeanConfigurationPropertiesWithListOfJavaType ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithSimpleList . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( javaBeanBinding ( SamplePropertiesWithSimpleList . class ) ) ;
assertThat ( typeHints ) . hasSize ( 1 ) ;
}
@Test
void processValueObjectConfigurationProperties ( ) {
RuntimeHints runtimeHints = process ( SampleImmutableProperties . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( valueObjectBinding ( SampleImmutableProperties . class ,
SampleImmutableProperties . class . getDeclaredConstructors ( ) [ 0 ] ) ) ;
assertThat ( typeHints ) . hasSize ( 1 ) ;
}
@Test
void processValueObjectConfigurationPropertiesWithSpecificConstructor ( ) throws NoSuchMethodException {
RuntimeHints runtimeHints = process ( SampleImmutablePropertiesWithSeveralConstructors . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( valueObjectBinding ( SampleImmutablePropertiesWithSeveralConstructors . class ,
SampleImmutablePropertiesWithSeveralConstructors . class . getDeclaredConstructor ( String . class ) ) ) ;
assertThat ( typeHints ) . hasSize ( 1 ) ;
}
@Test
void processValueObjectConfigurationPropertiesWithSeveralLayersOfPojo ( ) {
RuntimeHints runtimeHints = process ( SampleImmutablePropertiesWithList . class ) ;
List < TypeHint > typeHints = runtimeHints . reflection ( ) . typeHints ( ) . toList ( ) ;
assertThat ( typeHints ) . anySatisfy ( valueObjectBinding ( SampleImmutablePropertiesWithList . class ,
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 ) ;
}
@Test
void processConfigurationPropertiesWithNestedTypeNotUsedIsIgnored ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithNested . class ) ;
assertThat ( runtimeHints . reflection ( ) . getTypeHint ( SamplePropertiesWithNested . class ) )
. satisfies ( javaBeanBinding ( SamplePropertiesWithNested . class ) ) ;
}
@Test
void processConfigurationPropertiesWithNestedExternalType ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithExternalNested . class ) ;
assertThat ( runtimeHints . reflection ( ) . typeHints ( ) )
. anySatisfy ( javaBeanBinding ( SamplePropertiesWithExternalNested . class ) )
. anySatisfy ( javaBeanBinding ( SampleType . class ) )
. anySatisfy ( javaBeanBinding ( SampleType . Nested . class ) )
. hasSize ( 3 ) ;
}
@Test
void processConfigurationPropertiesWithRecursiveType ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithRecursive . class ) ;
assertThat ( runtimeHints . reflection ( ) . typeHints ( ) )
. anySatisfy ( javaBeanBinding ( SamplePropertiesWithRecursive . class ) )
. anySatisfy ( javaBeanBinding ( Recursive . class ) )
. hasSize ( 2 ) ;
}
@Test
void processValueObjectConfigurationPropertiesWithRecursiveType ( ) {
RuntimeHints runtimeHints = process ( SampleImmutablePropertiesWithRecursive . class ) ;
assertThat ( runtimeHints . reflection ( ) . typeHints ( ) )
. anySatisfy ( valueObjectBinding ( SampleImmutablePropertiesWithRecursive . class ,
SampleImmutablePropertiesWithRecursive . class . getDeclaredConstructors ( ) [ 0 ] ) )
. anySatisfy (
valueObjectBinding ( ImmutableRecursive . class , ImmutableRecursive . class . getDeclaredConstructors ( ) [ 0 ] ) )
. hasSize ( 2 ) ;
}
@Test
void processConfigurationPropertiesWithWellKnownTypes ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithWellKnownTypes . class ) ;
assertThat ( runtimeHints . reflection ( ) . typeHints ( ) )
. anySatisfy ( javaBeanBinding ( SamplePropertiesWithWellKnownTypes . class ) )
. hasSize ( 1 ) ;
}
@Test
void processConfigurationPropertiesWithCrossReference ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithCrossReference . class ) ;
assertThat ( runtimeHints . reflection ( ) . typeHints ( ) )
. anySatisfy ( javaBeanBinding ( SamplePropertiesWithCrossReference . class ) )
. anySatisfy ( javaBeanBinding ( CrossReferenceA . class ) )
. anySatisfy ( javaBeanBinding ( CrossReferenceB . class ) )
. hasSize ( 3 ) ;
}
@Test
void processConfigurationPropertiesWithUnresolvedGeneric ( ) {
RuntimeHints runtimeHints = process ( SamplePropertiesWithGeneric . class ) ;
assertThat ( runtimeHints . reflection ( ) . typeHints ( ) ) . anySatisfy ( javaBeanBinding ( SamplePropertiesWithGeneric . class ) )
. anySatisfy ( javaBeanBinding ( GenericObject . class ) ) ;
}
@Test
void processConfigurationPropertiesWithNestedGenerics ( ) {
RuntimeHints runtimeHints = process ( NestedGenerics . class ) ;
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 ) ) . 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 ) {
return javaBeanBinding ( type , type . getDeclaredConstructors ( ) [ 0 ] ) ;
}
private Consumer < TypeHint > javaBeanBinding ( Class < ? > type , Constructor < ? > constructor ) {
return ( entry ) - > {
assertThat ( entry . getType ( ) ) . isEqualTo ( TypeReference . of ( type ) ) ;
assertThat ( entry . constructors ( ) ) . singleElement ( ) . satisfies ( match ( constructor ) ) ;
assertThat ( entry . getMemberCategories ( ) ) . isEmpty ( ) ;
assertThat ( entry . methods ( ) ) . allMatch ( ( t ) - > t . getName ( ) . startsWith ( "set" ) | | t . getName ( ) . startsWith ( "get" )
| | t . getName ( ) . startsWith ( "is" ) ) ;
} ;
}
private Consumer < TypeHint > valueObjectBinding ( Class < ? > type , Constructor < ? > constructor ) {
return ( entry ) - > {
assertThat ( entry . getType ( ) ) . isEqualTo ( TypeReference . of ( type ) ) ;
assertThat ( entry . constructors ( ) ) . singleElement ( ) . satisfies ( match ( constructor ) ) ;
assertThat ( entry . getMemberCategories ( ) ) . isEmpty ( ) ;
assertThat ( entry . methods ( ) ) . isEmpty ( ) ;
} ;
}
private Consumer < ExecutableHint > match ( Constructor < ? > constructor ) {
return ( executableHint ) - > {
assertThat ( executableHint . getName ( ) ) . isEqualTo ( "<init>" ) ;
assertThat ( Arrays . stream ( constructor . getParameterTypes ( ) ) . map ( TypeReference : : of ) . toList ( ) )
. isEqualTo ( executableHint . getParameterTypes ( ) ) ;
} ;
Stream < TypeHint > typeHints ( ConfigurationPropertiesReflectionHintsContribution contribution ) {
GenerationContext generationContext = new TestGenerationContext ( ) ;
contribution . applyTo ( generationContext , mock ( BeanFactoryInitializationCode . class ) ) ;
return generationContext . getRuntimeHints ( ) . reflection ( ) . typeHints ( ) ;
}
private RuntimeHints process ( Class < ? > . . . types ) {
private ConfigurationPropertiesReflectionHintsContribution process ( Class < ? > . . . types ) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory ( ) ;
for ( Class < ? > type : types ) {
beanFactory . registerBeanDefinition ( type . getName ( ) , new RootBeanDefinition ( type ) ) ;
@ -278,12 +89,8 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
@@ -278,12 +89,8 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
return process ( beanFactory ) ;
}
private RuntimeHints process ( ConfigurableListableBeanFactory beanFactory ) {
BeanFactoryInitializationAotContribution contribution = this . processor . processAheadOfTime ( beanFactory ) ;
assertThat ( contribution ) . isNotNull ( ) ;
GenerationContext generationContext = new TestGenerationContext ( ) ;
contribution . applyTo ( generationContext , mock ( BeanFactoryInitializationCode . class ) ) ;
return generationContext . getRuntimeHints ( ) ;
private ConfigurationPropertiesReflectionHintsContribution process ( ConfigurableListableBeanFactory beanFactory ) {
return this . processor . processAheadOfTime ( beanFactory ) ;
}
@ConfigurationProperties ( "test" )
@ -291,375 +98,4 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
@@ -291,375 +98,4 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
}
@ConfigurationProperties ( "test" )
public static class SamplePropertiesWithSeveralConstructors {
SamplePropertiesWithSeveralConstructors ( ) {
}
SamplePropertiesWithSeveralConstructors ( String ignored ) {
}
}
@ConfigurationProperties ( "test" )
public static class SamplePropertiesWithMap {
public Map < String , Address > getAddresses ( ) {
return Collections . emptyMap ( ) ;
}
}
@ConfigurationProperties ( "test" )
public static class SamplePropertiesWithList {
public List < Address > getAllAddresses ( ) {
return Collections . emptyList ( ) ;
}
}
@ConfigurationProperties ( "test" )
public static class SamplePropertiesWithSimpleList {
public List < String > getNames ( ) {
return Collections . emptyList ( ) ;
}
}
@ConfigurationProperties ( "test" )
public static class SamplePropertiesWithArray {
public Address [ ] getAllAddresses ( ) {
return new Address [ 0 ] ;
}
}
@ConfigurationProperties
public static class SampleImmutableProperties {
@SuppressWarnings ( "unused" )
private final String name ;
SampleImmutableProperties ( String name ) {
this . name = name ;
}
}
@ConfigurationProperties
public static class SampleImmutablePropertiesWithSeveralConstructors {
@SuppressWarnings ( "unused" )
private final String name ;
@ConstructorBinding
SampleImmutablePropertiesWithSeveralConstructors ( String name ) {
this . name = name ;
}
SampleImmutablePropertiesWithSeveralConstructors ( ) {
this ( "test" ) ;
}
}
@ConfigurationProperties
public static class SampleImmutablePropertiesWithList {
@SuppressWarnings ( "unused" )
private final List < Person > family ;
SampleImmutablePropertiesWithList ( List < Person > family ) {
this . family = family ;
}
}
@ConfigurationProperties ( "nested" )
public static class SamplePropertiesWithNested {
static class OneLevelDown {
}
}
@ConfigurationProperties ( "nested" )
public static class SamplePropertiesWithExternalNested {
private String name ;
@NestedConfigurationProperty
private SampleType sampleType ;
public String getName ( ) {
return this . name ;
}
public void setName ( String name ) {
this . name = name ;
}
public SampleType getSampleType ( ) {
return this . sampleType ;
}
public void setSampleType ( SampleType sampleType ) {
this . sampleType = sampleType ;
}
}
@ConfigurationProperties ( "recursive" )
public static class SamplePropertiesWithRecursive {
@NestedConfigurationProperty
private Recursive recursive ;
public Recursive getRecursive ( ) {
return this . recursive ;
}
public void setRecursive ( Recursive recursive ) {
this . recursive = recursive ;
}
}
@ConfigurationProperties
public static class SampleImmutablePropertiesWithRecursive {
@NestedConfigurationProperty
private final ImmutableRecursive recursive ;
SampleImmutablePropertiesWithRecursive ( ImmutableRecursive recursive ) {
this . recursive = recursive ;
}
}
@ConfigurationProperties ( "wellKnownTypes" )
public static class SamplePropertiesWithWellKnownTypes implements ApplicationContextAware , EnvironmentAware {
private ApplicationContext applicationContext ;
private Environment environment ;
public ApplicationContext getApplicationContext ( ) {
return this . applicationContext ;
}
@Override
public void setApplicationContext ( ApplicationContext applicationContext ) {
this . applicationContext = applicationContext ;
}
public Environment getEnvironment ( ) {
return this . environment ;
}
@Override
public void setEnvironment ( Environment environment ) {
this . environment = environment ;
}
}
public static class SampleType {
private final Nested nested = new Nested ( ) ;
public Nested getNested ( ) {
return this . nested ;
}
static class Nested {
}
}
public static class Address {
}
public static class Person {
@SuppressWarnings ( "unused" )
private final String firstName ;
@SuppressWarnings ( "unused" )
private final String lastName ;
@NestedConfigurationProperty
private final Address address ;
Person ( String firstName , String lastName , Address address ) {
this . firstName = firstName ;
this . lastName = lastName ;
this . address = address ;
}
}
public static class Recursive {
private Recursive recursive ;
public Recursive getRecursive ( ) {
return this . recursive ;
}
public void setRecursive ( Recursive recursive ) {
this . recursive = recursive ;
}
}
public static class ImmutableRecursive {
@SuppressWarnings ( "unused" )
private final ImmutableRecursive recursive ;
ImmutableRecursive ( ImmutableRecursive recursive ) {
this . recursive = recursive ;
}
}
@ConfigurationProperties ( "crossreference" )
public static class SamplePropertiesWithCrossReference {
@NestedConfigurationProperty
private CrossReferenceA crossReferenceA ;
public void setCrossReferenceA ( CrossReferenceA crossReferenceA ) {
this . crossReferenceA = crossReferenceA ;
}
public CrossReferenceA getCrossReferenceA ( ) {
return this . crossReferenceA ;
}
}
public static class CrossReferenceA {
@NestedConfigurationProperty
private CrossReferenceB crossReferenceB ;
public void setCrossReferenceB ( CrossReferenceB crossReferenceB ) {
this . crossReferenceB = crossReferenceB ;
}
public CrossReferenceB getCrossReferenceB ( ) {
return this . crossReferenceB ;
}
}
public static class CrossReferenceB {
private CrossReferenceA crossReferenceA ;
public void setCrossReferenceA ( CrossReferenceA crossReferenceA ) {
this . crossReferenceA = crossReferenceA ;
}
public CrossReferenceA getCrossReferenceA ( ) {
return this . crossReferenceA ;
}
}
@ConfigurationProperties ( prefix = "generic" )
public static class SamplePropertiesWithGeneric {
@NestedConfigurationProperty
private GenericObject < ? > generic ;
public GenericObject < ? > getGeneric ( ) {
return this . generic ;
}
}
public static final class GenericObject < T > {
private final T value ;
GenericObject ( T value ) {
this . value = value ;
}
public T getValue ( ) {
return this . value ;
}
}
@ConfigurationProperties ( prefix = "nested-generics" )
public static class NestedGenerics {
private final Map < String , List < Nested > > nested = new HashMap < > ( ) ;
public Map < String , List < Nested > > getNested ( ) {
return this . nested ;
}
public static class Nested {
private String field ;
public String getField ( ) {
return this . field ;
}
public void setField ( String field ) {
this . field = field ;
}
}
}
@ConfigurationProperties ( prefix = "triple-nested" )
public static class TripleNested {
private final DoubleNested doubleNested = new DoubleNested ( ) ;
public DoubleNested getDoubleNested ( ) {
return this . doubleNested ;
}
public static class DoubleNested {
private final Nested nested = new Nested ( ) ;
public Nested getNested ( ) {
return this . nested ;
}
public static class Nested {
private String field ;
public String getField ( ) {
return this . field ;
}
public void setField ( String field ) {
this . field = field ;
}
}
}
}
}