1818
1919import java .lang .reflect .Method ;
2020import java .lang .reflect .Modifier ;
21+ import java .util .Map ;
2122import java .util .Set ;
2223
24+ import org .springframework .aot .generate .AccessControl ;
25+ import org .springframework .aot .generate .GeneratedClass ;
26+ import org .springframework .aot .generate .GeneratedMethod ;
27+ import org .springframework .aot .generate .GenerationContext ;
28+ import org .springframework .aot .generate .MethodReference .ArgumentCodeGenerator ;
29+ import org .springframework .aot .hint .ExecutableMode ;
30+ import org .springframework .beans .factory .aot .BeanFactoryInitializationAotContribution ;
31+ import org .springframework .beans .factory .aot .BeanFactoryInitializationAotProcessor ;
32+ import org .springframework .beans .factory .aot .BeanFactoryInitializationCode ;
33+ import org .springframework .beans .factory .aot .BeanRegistrationExcludeFilter ;
34+ import org .springframework .beans .factory .config .BeanDefinition ;
35+ import org .springframework .beans .factory .config .ConfigurableListableBeanFactory ;
2336import org .springframework .beans .factory .support .BeanDefinitionRegistry ;
37+ import org .springframework .beans .factory .support .DefaultListableBeanFactory ;
38+ import org .springframework .beans .factory .support .RegisteredBean ;
39+ import org .springframework .beans .factory .support .RootBeanDefinition ;
2440import org .springframework .boot .testcontainers .properties .TestcontainersPropertySource ;
2541import org .springframework .core .MethodIntrospector ;
2642import org .springframework .core .annotation .MergedAnnotations ;
43+ import org .springframework .core .env .ConfigurableEnvironment ;
2744import org .springframework .core .env .Environment ;
45+ import org .springframework .javapoet .CodeBlock ;
2846import org .springframework .test .context .DynamicPropertyRegistry ;
2947import org .springframework .test .context .DynamicPropertySource ;
3048import org .springframework .util .Assert ;
@@ -56,6 +74,16 @@ void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistr
5674 ReflectionUtils .makeAccessible (method );
5775 ReflectionUtils .invokeMethod (method , null , dynamicPropertyRegistry );
5876 });
77+
78+ String beanName = "importTestContainer.%s.%s" .formatted (DynamicPropertySource .class .getName (), definitionClass );
79+ if (!beanDefinitionRegistry .containsBeanDefinition (beanName )) {
80+ RootBeanDefinition bd = new RootBeanDefinition (DynamicPropertySourceMetadata .class );
81+ bd .setInstanceSupplier (() -> new DynamicPropertySourceMetadata (definitionClass , methods ));
82+ bd .setRole (BeanDefinition .ROLE_INFRASTRUCTURE );
83+ bd .setAutowireCandidate (false );
84+ bd .setAttribute (DynamicPropertySourceMetadata .class .getName (), true );
85+ beanDefinitionRegistry .registerBeanDefinition (beanName , bd );
86+ }
5987 }
6088
6189 private boolean isAnnotated (Method method ) {
@@ -71,4 +99,121 @@ private void assertValid(Method method) {
7199 + "' must accept a single DynamicPropertyRegistry argument" );
72100 }
73101
102+ private record DynamicPropertySourceMetadata (Class <?> definitionClass , Set <Method > methods ) {
103+ }
104+
105+ static class DynamicPropertySourceMetadataBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter {
106+
107+ @ Override
108+ public boolean isExcludedFromAotProcessing (RegisteredBean registeredBean ) {
109+ return registeredBean .getMergedBeanDefinition ().hasAttribute (DynamicPropertySourceMetadata .class .getName ());
110+ }
111+
112+ }
113+
114+ /**
115+ * {@link BeanFactoryInitializationAotProcessor} that generates methods for each
116+ * annotated {@link DynamicPropertySource} method.
117+ */
118+ static class DynamicPropertySourceBeanFactoryInitializationAotProcessor
119+ implements BeanFactoryInitializationAotProcessor {
120+
121+ @ Override
122+ public BeanFactoryInitializationAotContribution processAheadOfTime (
123+ ConfigurableListableBeanFactory beanFactory ) {
124+ Map <String , DynamicPropertySourceMetadata > metadata = beanFactory
125+ .getBeansOfType (DynamicPropertySourceMetadata .class );
126+ if (metadata .isEmpty ()) {
127+ return null ;
128+ }
129+ return new AotContibution (metadata );
130+ }
131+
132+ private static final class AotContibution implements BeanFactoryInitializationAotContribution {
133+
134+ private final Map <String , DynamicPropertySourceMetadata > metadata ;
135+
136+ private AotContibution (Map <String , DynamicPropertySourceMetadata > metadata ) {
137+ this .metadata = metadata ;
138+ }
139+
140+ @ Override
141+ public void applyTo (GenerationContext generationContext ,
142+ BeanFactoryInitializationCode beanFactoryInitializationCode ) {
143+ GeneratedMethod initializerMethod = beanFactoryInitializationCode .getMethods ()
144+ .add ("registerDynamicPropertySources" , (code ) -> {
145+ code .addJavadoc ("Registers {@code @DynamicPropertySource} properties" );
146+ code .addParameter (ConfigurableEnvironment .class , "environment" );
147+ code .addParameter (DefaultListableBeanFactory .class , "beanFactory" );
148+ code .addModifiers (javax .lang .model .element .Modifier .PRIVATE ,
149+ javax .lang .model .element .Modifier .STATIC );
150+ code .addStatement ("$T dynamicPropertyRegistry = $T.attach(environment, beanFactory)" ,
151+ DynamicPropertyRegistry .class , TestcontainersPropertySource .class );
152+ this .metadata .forEach ((name , metadata ) -> {
153+ GeneratedMethod dynamicPropertySourceMethod = generateMethods (generationContext , metadata );
154+ code .addStatement (dynamicPropertySourceMethod .toMethodReference ()
155+ .toInvokeCodeBlock (ArgumentCodeGenerator .of (DynamicPropertyRegistry .class ,
156+ "dynamicPropertyRegistry" )));
157+ });
158+ });
159+ beanFactoryInitializationCode .addInitializer (initializerMethod .toMethodReference ());
160+ }
161+
162+ // Generates a new class in definition class package and invokes
163+ // all @DynamicPropertySource methods.
164+ private GeneratedMethod generateMethods (GenerationContext generationContext ,
165+ DynamicPropertySourceMetadata metadata ) {
166+ Class <?> definitionClass = metadata .definitionClass ();
167+ GeneratedClass generatedClass = generationContext .getGeneratedClasses ()
168+ .addForFeatureComponent (DynamicPropertySource .class .getSimpleName (), definitionClass ,
169+ (code ) -> code .addModifiers (javax .lang .model .element .Modifier .PUBLIC ));
170+ return generatedClass .getMethods ().add ("registerDynamicPropertySource" , (code ) -> {
171+ code .addJavadoc ("Registers {@code @DynamicPropertySource} properties for class '$L'" ,
172+ definitionClass .getName ());
173+ code .addParameter (DynamicPropertyRegistry .class , "dynamicPropertyRegistry" );
174+ code .addModifiers (javax .lang .model .element .Modifier .PUBLIC ,
175+ javax .lang .model .element .Modifier .STATIC );
176+ metadata .methods ().forEach ((method ) -> {
177+ GeneratedMethod generateMethod = generateMethod (generationContext , generatedClass ,
178+ definitionClass , method );
179+ code .addStatement (generateMethod .toMethodReference ()
180+ .toInvokeCodeBlock (ArgumentCodeGenerator .of (DynamicPropertyRegistry .class ,
181+ "dynamicPropertyRegistry" )));
182+ });
183+ });
184+ }
185+
186+ // If the method is inaccessible, the reflection will be used; otherwise,
187+ // direct call to the method will be used.
188+ private static GeneratedMethod generateMethod (GenerationContext generationContext ,
189+ GeneratedClass generatedClass , Class <?> definitionClass , Method method ) {
190+ return generatedClass .getMethods ().add (method .getName (), (code ) -> {
191+ code .addJavadoc ("Register {@code @DynamicPropertySource} for method '$L.$L'" ,
192+ method .getDeclaringClass ().getName (), method .getName ());
193+ code .addModifiers (javax .lang .model .element .Modifier .PRIVATE ,
194+ javax .lang .model .element .Modifier .STATIC );
195+ code .addParameter (DynamicPropertyRegistry .class , "dynamicPropertyRegistry" );
196+ if (AccessControl .forMember (method ).isAccessibleFrom (generatedClass .getName ())) {
197+ code .addStatement (
198+ CodeBlock .of ("$T.$L(dynamicPropertyRegistry)" , definitionClass , method .getName ()));
199+ }
200+ else {
201+ generationContext .getRuntimeHints ().reflection ().registerMethod (method , ExecutableMode .INVOKE );
202+ code .addStatement ("$T method = $T.findMethod($T.class, $S, $T.class)" , Method .class ,
203+ ReflectionUtils .class , definitionClass , method .getName (),
204+ DynamicPropertyRegistry .class );
205+ code .addStatement ("$T.notNull(method, $S)" , Assert .class ,
206+ "Method '" + method .getName () + "' is not found" );
207+ code .addStatement ("$T.makeAccessible(method)" , ReflectionUtils .class );
208+ code .addStatement ("$T.invokeMethod(method, null, dynamicPropertyRegistry)" ,
209+ ReflectionUtils .class );
210+ }
211+ });
212+
213+ }
214+
215+ }
216+
217+ }
218+
74219}
0 commit comments