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,121 @@ 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
+ 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
+
74
219
}
0 commit comments