18
18
19
19
import java .lang .reflect .Method ;
20
20
import java .lang .reflect .Modifier ;
21
+ import java .util .LinkedHashSet ;
22
+ import java .util .Map ;
21
23
import java .util .Set ;
22
24
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 ;
23
34
import 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 ;
24
38
import org .springframework .boot .testcontainers .properties .TestcontainersPropertySource ;
25
39
import org .springframework .core .MethodIntrospector ;
26
40
import org .springframework .core .annotation .MergedAnnotations ;
41
+ import org .springframework .core .env .ConfigurableEnvironment ;
27
42
import org .springframework .core .env .Environment ;
28
43
import org .springframework .test .context .DynamicPropertyRegistry ;
29
44
import org .springframework .test .context .DynamicPropertySource ;
30
45
import org .springframework .util .Assert ;
46
+ import org .springframework .util .ClassUtils ;
31
47
import org .springframework .util .ReflectionUtils ;
32
48
33
49
/**
@@ -51,11 +67,22 @@ void registerDynamicPropertySources(BeanDefinitionRegistry beanDefinitionRegistr
51
67
}
52
68
DynamicPropertyRegistry dynamicPropertyRegistry = TestcontainersPropertySource .attach (this .environment ,
53
69
beanDefinitionRegistry );
70
+ DynamicPropertySourceMethodsImporterMetadata metadata = new DynamicPropertySourceMethodsImporterMetadata ();
54
71
methods .forEach ((method ) -> {
55
72
assertValid (method );
56
73
ReflectionUtils .makeAccessible (method );
57
74
ReflectionUtils .invokeMethod (method , null , dynamicPropertyRegistry );
75
+ metadata .methods .add (method );
58
76
});
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
+ }
59
86
}
60
87
61
88
private boolean isAnnotated (Method method ) {
@@ -71,4 +98,86 @@ private void assertValid(Method method) {
71
98
+ "' must accept a single DynamicPropertyRegistry argument" );
72
99
}
73
100
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
+
74
183
}
0 commit comments