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 .beans .factory .aot .BeanFactoryInitializationAotContribution ;
30
+ import org .springframework .beans .factory .aot .BeanFactoryInitializationAotProcessor ;
31
+ import org .springframework .beans .factory .aot .BeanFactoryInitializationCode ;
32
+ import org .springframework .beans .factory .aot .BeanRegistrationExcludeFilter ;
33
+ import org .springframework .beans .factory .config .BeanDefinition ;
34
+ import org .springframework .beans .factory .config .ConfigurableListableBeanFactory ;
23
35
import org .springframework .beans .factory .support .BeanDefinitionRegistry ;
36
+ import org .springframework .beans .factory .support .DefaultListableBeanFactory ;
37
+ import org .springframework .beans .factory .support .RegisteredBean ;
38
+ import org .springframework .beans .factory .support .RootBeanDefinition ;
24
39
import org .springframework .boot .testcontainers .properties .TestcontainersPropertySource ;
25
40
import org .springframework .core .MethodIntrospector ;
26
41
import org .springframework .core .annotation .MergedAnnotations ;
42
+ import org .springframework .core .env .ConfigurableEnvironment ;
27
43
import org .springframework .core .env .Environment ;
44
+ import org .springframework .javapoet .CodeBlock ;
28
45
import org .springframework .test .context .DynamicPropertyRegistry ;
29
46
import org .springframework .test .context .DynamicPropertySource ;
30
47
import org .springframework .util .Assert ;
@@ -51,11 +68,22 @@ void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistr
51
68
}
52
69
DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource .attach (this .environment ,
53
70
beanDefinitionRegistry );
71
+
54
72
methods .forEach ((method ) -> {
55
73
assertValid (method );
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,119 @@ 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 (definitionClass , method , generatedClass );
178
+ code .addStatement (generateMethod .toMethodReference ()
179
+ .toInvokeCodeBlock (ArgumentCodeGenerator .of (DynamicPropertyRegistry .class ,
180
+ "dynamicPropertyRegistry" )));
181
+ });
182
+ });
183
+ }
184
+
185
+ // If the method is inaccessible, the reflection will be used; otherwise,
186
+ // direct call to the method will be used.
187
+ private static GeneratedMethod generateMethod (Class <?> definitionClass , Method method ,
188
+ GeneratedClass generatedClass ) {
189
+ return generatedClass .getMethods ().add (method .getName (), (code ) -> {
190
+ code .addJavadoc ("Register {@code @DynamicPropertySource} for method '$L.$L'" ,
191
+ method .getDeclaringClass ().getName (), method .getName ());
192
+ code .addModifiers (javax .lang .model .element .Modifier .PRIVATE ,
193
+ javax .lang .model .element .Modifier .STATIC );
194
+ code .addParameter (DynamicPropertyRegistry .class , "dynamicPropertyRegistry" );
195
+ if (AccessControl .forMember (method ).isAccessibleFrom (generatedClass .getName ())) {
196
+ code .addStatement (
197
+ CodeBlock .of ("$T.$L(dynamicPropertyRegistry)" , definitionClass , method .getName ()));
198
+ }
199
+ else {
200
+ code .addStatement ("$T method = $T.findMethod($T.class, $S, $T.class)" , Method .class ,
201
+ ReflectionUtils .class , definitionClass , method .getName (),
202
+ DynamicPropertyRegistry .class );
203
+ code .addStatement ("$T.notNull(method, $S)" , Assert .class ,
204
+ "Method '" + method .getName () + "' is not found" );
205
+ code .addStatement ("$T.makeAccessible(method)" , ReflectionUtils .class );
206
+ code .addStatement ("$T.invokeMethod(method, null, dynamicPropertyRegistry)" ,
207
+ ReflectionUtils .class );
208
+ }
209
+ });
210
+
211
+ }
212
+
213
+ }
214
+
215
+ }
216
+
74
217
}
0 commit comments