18
18
19
19
import java .lang .reflect .Method ;
20
20
import java .lang .reflect .Modifier ;
21
+ import java .util .Map ;
21
22
import java .util .Set ;
22
23
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 ;
23
36
import 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 ;
24
40
import org .springframework .boot .testcontainers .properties .TestcontainersPropertySource ;
25
41
import org .springframework .core .MethodIntrospector ;
26
42
import org .springframework .core .annotation .MergedAnnotations ;
43
+ import org .springframework .core .env .ConfigurableEnvironment ;
27
44
import org .springframework .core .env .Environment ;
45
+ import org .springframework .javapoet .CodeBlock ;
28
46
import org .springframework .test .context .DynamicPropertyRegistry ;
29
47
import org .springframework .test .context .DynamicPropertySource ;
30
48
import org .springframework .util .Assert ;
@@ -56,6 +74,16 @@ void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistr
56
74
ReflectionUtils .makeAccessible (method );
57
75
ReflectionUtils .invokeMethod (method , null , dynamicPropertyRegistry );
58
76
});
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
+ }
59
87
}
60
88
61
89
private boolean isAnnotated (Method method ) {
@@ -71,4 +99,126 @@ private void assertValid(Method method) {
71
99
+ "' must accept a single DynamicPropertyRegistry argument" );
72
100
}
73
101
102
+ private record DynamicPropertySourceMetadata (Class <?> definitionClass , Set <Method > methods ) {
103
+ }
104
+
105
+ /**
106
+ * {@link BeanRegistrationExcludeFilter} to exclude
107
+ * {@link DynamicPropertySourceMetadata} from AOT bean registrations.
108
+ */
109
+ static class DynamicPropertySourceMetadataBeanRegistrationExcludeFilter implements BeanRegistrationExcludeFilter {
110
+
111
+ @ Override
112
+ public boolean isExcludedFromAotProcessing (RegisteredBean registeredBean ) {
113
+ return registeredBean .getMergedBeanDefinition ().hasAttribute (DynamicPropertySourceMetadata .class .getName ());
114
+ }
115
+
116
+ }
117
+
118
+ /**
119
+ * The {@link BeanFactoryInitializationAotProcessor} generates methods for each
120
+ * {@code @DynamicPropertySource-annotated} method.
121
+ *
122
+ */
123
+ static class DynamicPropertySourceBeanFactoryInitializationAotProcessor
124
+ implements BeanFactoryInitializationAotProcessor {
125
+
126
+ @ Override
127
+ public BeanFactoryInitializationAotContribution processAheadOfTime (
128
+ ConfigurableListableBeanFactory beanFactory ) {
129
+ Map <String , DynamicPropertySourceMetadata > metadata = beanFactory
130
+ .getBeansOfType (DynamicPropertySourceMetadata .class , false , false );
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 , DynamicPropertySourceMetadata > metadata ;
140
+
141
+ private AotContibution (Map <String , DynamicPropertySourceMetadata > metadata ) {
142
+ this .metadata = metadata ;
143
+ }
144
+
145
+ @ Override
146
+ public void applyTo (GenerationContext generationContext ,
147
+ BeanFactoryInitializationCode beanFactoryInitializationCode ) {
148
+ GeneratedMethod initializerMethod = beanFactoryInitializationCode .getMethods ()
149
+ .add ("registerDynamicPropertySources" , (code ) -> {
150
+ code .addJavadoc ("Registers {@code @DynamicPropertySource} properties" );
151
+ code .addParameter (ConfigurableEnvironment .class , "environment" );
152
+ code .addParameter (DefaultListableBeanFactory .class , "beanFactory" );
153
+ code .addModifiers (javax .lang .model .element .Modifier .PRIVATE ,
154
+ javax .lang .model .element .Modifier .STATIC );
155
+ code .addStatement ("$T dynamicPropertyRegistry = $T.attach(environment, beanFactory)" ,
156
+ DynamicPropertyRegistry .class , TestcontainersPropertySource .class );
157
+ this .metadata .forEach ((name , metadata ) -> {
158
+ GeneratedMethod dynamicPropertySourceMethod = generateMethods (generationContext , metadata );
159
+ code .addStatement (dynamicPropertySourceMethod .toMethodReference ()
160
+ .toInvokeCodeBlock (ArgumentCodeGenerator .of (DynamicPropertyRegistry .class ,
161
+ "dynamicPropertyRegistry" )));
162
+ });
163
+ });
164
+ beanFactoryInitializationCode .addInitializer (initializerMethod .toMethodReference ());
165
+ }
166
+
167
+ // Generates a new class in definition class package and invokes
168
+ // all @DynamicPropertySource methods.
169
+ private GeneratedMethod generateMethods (GenerationContext generationContext ,
170
+ DynamicPropertySourceMetadata metadata ) {
171
+ Class <?> definitionClass = metadata .definitionClass ();
172
+ GeneratedClass generatedClass = generationContext .getGeneratedClasses ()
173
+ .addForFeatureComponent (DynamicPropertySource .class .getSimpleName (), definitionClass ,
174
+ (code ) -> code .addModifiers (javax .lang .model .element .Modifier .PUBLIC ));
175
+ return generatedClass .getMethods ().add ("registerDynamicPropertySource" , (code ) -> {
176
+ code .addJavadoc ("Registers {@code @DynamicPropertySource} properties for class '$L'" ,
177
+ definitionClass .getName ());
178
+ code .addParameter (DynamicPropertyRegistry .class , "dynamicPropertyRegistry" );
179
+ code .addModifiers (javax .lang .model .element .Modifier .PUBLIC ,
180
+ javax .lang .model .element .Modifier .STATIC );
181
+ metadata .methods ().forEach ((method ) -> {
182
+ GeneratedMethod generateMethod = generateMethod (generationContext , generatedClass ,
183
+ definitionClass , method );
184
+ code .addStatement (generateMethod .toMethodReference ()
185
+ .toInvokeCodeBlock (ArgumentCodeGenerator .of (DynamicPropertyRegistry .class ,
186
+ "dynamicPropertyRegistry" )));
187
+ });
188
+ });
189
+ }
190
+
191
+ // If the method is inaccessible, the reflection will be used; otherwise,
192
+ // direct call to the method will be used.
193
+ private static GeneratedMethod generateMethod (GenerationContext generationContext ,
194
+ GeneratedClass generatedClass , Class <?> definitionClass , Method method ) {
195
+ return generatedClass .getMethods ().add (method .getName (), (code ) -> {
196
+ code .addJavadoc ("Register {@code @DynamicPropertySource} for method '$L.$L'" ,
197
+ method .getDeclaringClass ().getName (), method .getName ());
198
+ code .addModifiers (javax .lang .model .element .Modifier .PRIVATE ,
199
+ javax .lang .model .element .Modifier .STATIC );
200
+ code .addParameter (DynamicPropertyRegistry .class , "dynamicPropertyRegistry" );
201
+ if (AccessControl .forMember (method ).isAccessibleFrom (generatedClass .getName ())) {
202
+ code .addStatement (
203
+ CodeBlock .of ("$T.$L(dynamicPropertyRegistry)" , definitionClass , method .getName ()));
204
+ }
205
+ else {
206
+ generationContext .getRuntimeHints ().reflection ().registerMethod (method , ExecutableMode .INVOKE );
207
+ code .addStatement ("$T method = $T.findMethod($T.class, $S, $T.class)" , Method .class ,
208
+ ReflectionUtils .class , definitionClass , method .getName (),
209
+ DynamicPropertyRegistry .class );
210
+ code .addStatement ("$T.notNull(method, $S)" , Assert .class ,
211
+ "Method '" + method .getName () + "' is not found" );
212
+ code .addStatement ("$T.makeAccessible(method)" , ReflectionUtils .class );
213
+ code .addStatement ("$T.invokeMethod(method, null, dynamicPropertyRegistry)" ,
214
+ ReflectionUtils .class );
215
+ }
216
+ });
217
+
218
+ }
219
+
220
+ }
221
+
222
+ }
223
+
74
224
}
0 commit comments