1818
1919import java .lang .reflect .Method ;
2020import java .lang .reflect .Modifier ;
21+ import java .util .LinkedHashSet ;
22+ import java .util .Map ;
2123import java .util .Set ;
2224
25+ import org .springframework .aot .generate .GeneratedMethod ;
26+ import org .springframework .aot .generate .GenerationContext ;
27+ import org .springframework .aot .hint .ExecutableMode ;
28+ import org .springframework .beans .factory .aot .BeanFactoryInitializationAotContribution ;
29+ import org .springframework .beans .factory .aot .BeanFactoryInitializationAotProcessor ;
30+ import org .springframework .beans .factory .aot .BeanFactoryInitializationCode ;
31+ import org .springframework .beans .factory .aot .BeanRegistrationExcludeFilter ;
32+ import org .springframework .beans .factory .config .BeanDefinition ;
33+ import org .springframework .beans .factory .config .ConfigurableListableBeanFactory ;
2334import org .springframework .beans .factory .support .BeanDefinitionRegistry ;
35+ import org .springframework .beans .factory .support .DefaultListableBeanFactory ;
36+ import org .springframework .beans .factory .support .RegisteredBean ;
37+ import org .springframework .beans .factory .support .RootBeanDefinition ;
2438import org .springframework .boot .testcontainers .properties .TestcontainersPropertySource ;
2539import org .springframework .core .MethodIntrospector ;
2640import org .springframework .core .annotation .MergedAnnotations ;
41+ import org .springframework .core .env .ConfigurableEnvironment ;
2742import org .springframework .core .env .Environment ;
2843import org .springframework .test .context .DynamicPropertyRegistry ;
2944import org .springframework .test .context .DynamicPropertySource ;
3045import org .springframework .util .Assert ;
46+ import org .springframework .util .ClassUtils ;
3147import org .springframework .util .ReflectionUtils ;
3248
3349/**
@@ -51,11 +67,22 @@ void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistr
5167 }
5268 DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource .attach (this .environment ,
5369 beanDefinitionRegistry );
70+ DynamicPropertySourceMethodsImporterMetadata metadata = new DynamicPropertySourceMethodsImporterMetadata ();
5471 methods .forEach ((method ) -> {
5572 assertValid (method );
5673 ReflectionUtils .makeAccessible (method );
5774 ReflectionUtils .invokeMethod (method , null , dynamicPropertyRegistry );
75+ metadata .methods .add (method );
5876 });
77+ String beanName = "importTestContainer.%s.%s" .formatted (DynamicPropertySource .class .getName (), definitionClass );
78+ if (!beanDefinitionRegistry .containsBeanDefinition (beanName )) {
79+ RootBeanDefinition bd = new RootBeanDefinition (DynamicPropertySourceMethodsImporterMetadata .class );
80+ bd .setInstanceSupplier (() -> metadata );
81+ bd .setRole (BeanDefinition .ROLE_INFRASTRUCTURE );
82+ bd .setAutowireCandidate (false );
83+ bd .setAttribute (DynamicPropertySourceMethodsImporterMetadata .class .getName (), true );
84+ beanDefinitionRegistry .registerBeanDefinition (beanName , bd );
85+ }
5986 }
6087
6188 private boolean isAnnotated (Method method ) {
@@ -71,4 +98,86 @@ private void assertValid(Method method) {
7198 + "' must accept a single DynamicPropertyRegistry argument" );
7299 }
73100
101+ private static final class DynamicPropertySourceMethodsImporterMetadata {
102+
103+ private final Set <Method > methods = new LinkedHashSet <>();
104+
105+ }
106+
107+ static class DynamicPropertySourceMethodsImporterMetadataBeanRegistrationExcludeFilter
108+ implements BeanRegistrationExcludeFilter {
109+
110+ @ Override
111+ public boolean isExcludedFromAotProcessing (RegisteredBean registeredBean ) {
112+ return registeredBean .getMergedBeanDefinition ()
113+ .hasAttribute (DynamicPropertySourceMethodsImporterMetadata .class .getName ());
114+ }
115+
116+ }
117+
118+ /**
119+ * {@link BeanFactoryInitializationAotProcessor} that generates all
120+ * {@link DynamicPropertySource} methods if any.
121+ *
122+ */
123+ static class DynamicPropertySourceBeanFactoryInitializationAotProcessor
124+ implements BeanFactoryInitializationAotProcessor {
125+
126+ @ Override
127+ public BeanFactoryInitializationAotContribution processAheadOfTime (
128+ ConfigurableListableBeanFactory beanFactory ) {
129+ Map <String , DynamicPropertySourceMethodsImporterMetadata > metadata = beanFactory
130+ .getBeansOfType (DynamicPropertySourceMethodsImporterMetadata .class );
131+ if (metadata .isEmpty ()) {
132+ return null ;
133+ }
134+ return new AotContibution (metadata );
135+ }
136+
137+ private static final class AotContibution implements BeanFactoryInitializationAotContribution {
138+
139+ private final Map <String , DynamicPropertySourceMethodsImporterMetadata > metadata ;
140+
141+ private AotContibution (Map <String , DynamicPropertySourceMethodsImporterMetadata > metadata ) {
142+ this .metadata = metadata ;
143+ }
144+
145+ @ Override
146+ public void applyTo (GenerationContext generationContext ,
147+ BeanFactoryInitializationCode beanFactoryInitializationCode ) {
148+ this .metadata .forEach ((name , metadata ) -> metadata .methods .forEach ((method ) -> {
149+ generationContext .getRuntimeHints ().reflection ().registerMethod (method , ExecutableMode .INVOKE );
150+ GeneratedMethod generatedMethod = beanFactoryInitializationCode .getMethods ()
151+ .add (method .getName (), (code ) -> {
152+ code .addJavadoc ("DynamicPropertySource for method $L.$L" ,
153+ method .getDeclaringClass ().getName (), method .getName ());
154+ code .addModifiers (javax .lang .model .element .Modifier .PRIVATE ,
155+ javax .lang .model .element .Modifier .STATIC );
156+ code .addParameter (ConfigurableEnvironment .class , "environment" );
157+ code .addParameter (DefaultListableBeanFactory .class , "beanFactory" );
158+ code .addStatement ("$T dynamicPropertyRegistry = $T.attach(environment, beanFactory)" ,
159+ DynamicPropertyRegistry .class , TestcontainersPropertySource .class );
160+ code .beginControlFlow ("try" );
161+ code .addStatement ("$T<?> clazz = $T.forName($S, beanFactory.getBeanClassLoader())" ,
162+ Class .class , ClassUtils .class , method .getDeclaringClass ().getName ());
163+ code .addStatement ("$T method = $T.findMethod(clazz, $S, $T.class)" , Method .class ,
164+ ReflectionUtils .class , method .getName (), DynamicPropertyRegistry .class );
165+ code .addStatement ("$T.notNull(method, $S)" , Assert .class ,
166+ "Method '" + method .getName () + "' is not found" );
167+ code .addStatement ("$T.makeAccessible(method)" , ReflectionUtils .class );
168+ code .addStatement ("$T.invokeMethod(method, null, dynamicPropertyRegistry)" ,
169+ ReflectionUtils .class );
170+ code .nextControlFlow ("catch ($T ex)" , ClassNotFoundException .class );
171+ code .addStatement ("throw new $T(ex)" , RuntimeException .class );
172+ code .endControlFlow ();
173+ });
174+ beanFactoryInitializationCode .addInitializer (generatedMethod .toMethodReference ());
175+ }));
176+
177+ }
178+
179+ }
180+
181+ }
182+
74183}
0 commit comments