16
16
17
17
package org .springframework .boot .actuate .autoconfigure .jdbc ;
18
18
19
+ import java .io .PrintWriter ;
20
+ import java .sql .Connection ;
21
+ import java .sql .ConnectionBuilder ;
22
+ import java .sql .SQLException ;
23
+ import java .sql .SQLFeatureNotSupportedException ;
24
+ import java .sql .ShardingKeyBuilder ;
19
25
import java .util .HashMap ;
20
26
import java .util .Map ;
27
+ import java .util .logging .Logger ;
21
28
22
29
import javax .sql .DataSource ;
23
30
24
31
import org .junit .jupiter .api .Test ;
25
32
33
+ import org .springframework .beans .BeansException ;
34
+ import org .springframework .beans .factory .config .BeanPostProcessor ;
26
35
import org .springframework .boot .actuate .autoconfigure .health .HealthContributorAutoConfiguration ;
27
36
import org .springframework .boot .actuate .autoconfigure .jdbc .DataSourceHealthContributorAutoConfiguration .RoutingDataSourceHealthContributor ;
28
37
import org .springframework .boot .actuate .health .CompositeHealthContributor ;
@@ -87,6 +96,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource() {
87
96
});
88
97
}
89
98
99
+ @ Test
100
+ void runWithProxyBeanPostProcessorRoutingAndEmbeddedDataSourceShouldIncludeRoutingDataSource () {
101
+ this .contextRunner
102
+ .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , EmbeddedDataSourceConfiguration .class ,
103
+ RoutingDataSourceConfig .class )
104
+ .run ((context ) -> {
105
+ CompositeHealthContributor composite = context .getBean (CompositeHealthContributor .class );
106
+ assertThat (composite .getContributor ("dataSource" )).isInstanceOf (DataSourceHealthIndicator .class );
107
+ assertThat (composite .getContributor ("routingDataSource" ))
108
+ .isInstanceOf (RoutingDataSourceHealthContributor .class );
109
+ });
110
+ }
111
+
90
112
@ Test
91
113
void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
92
114
this .contextRunner .withUserConfiguration (EmbeddedDataSourceConfiguration .class , RoutingDataSourceConfig .class )
@@ -98,6 +120,19 @@ void runWithRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgn
98
120
});
99
121
}
100
122
123
+ @ Test
124
+ void runWithProxyBeanPostProcessorAndRoutingAndEmbeddedDataSourceShouldNotIncludeRoutingDataSourceWhenIgnored () {
125
+ this .contextRunner
126
+ .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , EmbeddedDataSourceConfiguration .class ,
127
+ RoutingDataSourceConfig .class )
128
+ .withPropertyValues ("management.health.db.ignore-routing-datasources:true" )
129
+ .run ((context ) -> {
130
+ assertThat (context ).doesNotHaveBean (CompositeHealthContributor .class );
131
+ assertThat (context ).hasSingleBean (DataSourceHealthIndicator .class );
132
+ assertThat (context ).doesNotHaveBean (RoutingDataSourceHealthContributor .class );
133
+ });
134
+ }
135
+
101
136
@ Test
102
137
void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
103
138
this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class ).run ((context ) -> {
@@ -112,6 +147,23 @@ void runWithOnlyRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndic
112
147
});
113
148
}
114
149
150
+ @ Test
151
+ void runWithProxyBeanPostProcessorAndRoutingDataSourceShouldIncludeRoutingDataSourceWithComposedIndicators () {
152
+ this .contextRunner .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , RoutingDataSourceConfig .class )
153
+ .run ((context ) -> {
154
+ assertThat (context ).hasSingleBean (RoutingDataSourceHealthContributor .class );
155
+ RoutingDataSourceHealthContributor routingHealthContributor = context
156
+ .getBean (RoutingDataSourceHealthContributor .class );
157
+ assertThat (routingHealthContributor .getContributor ("one" ))
158
+ .isInstanceOf (DataSourceHealthIndicator .class );
159
+ assertThat (routingHealthContributor .getContributor ("two" ))
160
+ .isInstanceOf (DataSourceHealthIndicator .class );
161
+ assertThat (routingHealthContributor .iterator ()).toIterable ()
162
+ .extracting ("name" )
163
+ .containsExactlyInAnyOrder ("one" , "two" );
164
+ });
165
+ }
166
+
115
167
@ Test
116
168
void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored () {
117
169
this .contextRunner .withUserConfiguration (RoutingDataSourceConfig .class )
@@ -121,6 +173,15 @@ void runWithOnlyRoutingDataSourceShouldCrashWhenIgnored() {
121
173
.hasRootCauseInstanceOf (IllegalArgumentException .class ));
122
174
}
123
175
176
+ @ Test
177
+ void runWithProxyBeanPostProcessorAndOnlyRoutingDataSourceShouldCrashWhenIgnored () {
178
+ this .contextRunner .withUserConfiguration (ProxyDataSourceBeanPostProcessor .class , RoutingDataSourceConfig .class )
179
+ .withPropertyValues ("management.health.db.ignore-routing-datasources:true" )
180
+ .run ((context ) -> assertThat (context ).hasFailed ()
181
+ .getFailure ()
182
+ .hasRootCauseInstanceOf (IllegalArgumentException .class ));
183
+ }
184
+
124
185
@ Test
125
186
void runWithValidationQueryPropertyShouldUseCustomQuery () {
126
187
this .contextRunner
@@ -177,30 +238,112 @@ DataSource testDataSource() {
177
238
static class RoutingDataSourceConfig {
178
239
179
240
@ Bean
180
- AbstractRoutingDataSource routingDataSource () {
241
+ AbstractRoutingDataSource routingDataSource () throws SQLException {
181
242
Map <Object , DataSource > dataSources = new HashMap <>();
182
243
dataSources .put ("one" , mock (DataSource .class ));
183
244
dataSources .put ("two" , mock (DataSource .class ));
184
245
AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
246
+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
247
+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
185
248
given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
186
249
return routingDataSource ;
187
250
}
188
251
189
252
}
190
253
254
+ static class ProxyDataSourceBeanPostProcessor implements BeanPostProcessor {
255
+
256
+ @ Override
257
+ public Object postProcessBeforeInitialization (Object bean , String beanName ) throws BeansException {
258
+ if (bean instanceof DataSource dataSource ) {
259
+ return new ProxyDataSource (dataSource );
260
+ }
261
+ return bean ;
262
+ }
263
+
264
+ }
265
+
191
266
@ Configuration (proxyBeanMethods = false )
192
267
static class NullKeyRoutingDataSourceConfig {
193
268
194
269
@ Bean
195
- AbstractRoutingDataSource routingDataSource () {
270
+ AbstractRoutingDataSource routingDataSource () throws Exception {
196
271
Map <Object , DataSource > dataSources = new HashMap <>();
197
272
dataSources .put (null , mock (DataSource .class ));
198
273
dataSources .put ("one" , mock (DataSource .class ));
199
274
AbstractRoutingDataSource routingDataSource = mock (AbstractRoutingDataSource .class );
275
+ given (routingDataSource .isWrapperFor (AbstractRoutingDataSource .class )).willReturn (true );
276
+ given (routingDataSource .unwrap (AbstractRoutingDataSource .class )).willReturn (routingDataSource );
200
277
given (routingDataSource .getResolvedDataSources ()).willReturn (dataSources );
201
278
return routingDataSource ;
202
279
}
203
280
204
281
}
205
282
283
+ static class ProxyDataSource implements DataSource {
284
+
285
+ private final DataSource dataSource ;
286
+
287
+ ProxyDataSource (DataSource dataSource ) {
288
+ this .dataSource = dataSource ;
289
+ }
290
+
291
+ @ Override
292
+ public void setLogWriter (PrintWriter out ) throws SQLException {
293
+ this .dataSource .setLogWriter (out );
294
+ }
295
+
296
+ @ Override
297
+ public Connection getConnection () throws SQLException {
298
+ return this .dataSource .getConnection ();
299
+ }
300
+
301
+ @ Override
302
+ public Connection getConnection (String username , String password ) throws SQLException {
303
+ return this .dataSource .getConnection (username , password );
304
+ }
305
+
306
+ @ Override
307
+ public PrintWriter getLogWriter () throws SQLException {
308
+ return this .dataSource .getLogWriter ();
309
+ }
310
+
311
+ @ Override
312
+ public void setLoginTimeout (int seconds ) throws SQLException {
313
+ this .dataSource .setLoginTimeout (seconds );
314
+ }
315
+
316
+ @ Override
317
+ public int getLoginTimeout () throws SQLException {
318
+ return this .dataSource .getLoginTimeout ();
319
+ }
320
+
321
+ @ Override
322
+ public ConnectionBuilder createConnectionBuilder () throws SQLException {
323
+ return this .dataSource .createConnectionBuilder ();
324
+ }
325
+
326
+ @ Override
327
+ public Logger getParentLogger () throws SQLFeatureNotSupportedException {
328
+ return this .dataSource .getParentLogger ();
329
+ }
330
+
331
+ @ Override
332
+ public ShardingKeyBuilder createShardingKeyBuilder () throws SQLException {
333
+ return this .dataSource .createShardingKeyBuilder ();
334
+ }
335
+
336
+ @ Override
337
+ @ SuppressWarnings ("unchecked" )
338
+ public <T > T unwrap (Class <T > iface ) throws SQLException {
339
+ return iface .isInstance (this ) ? (T ) this : this .dataSource .unwrap (iface );
340
+ }
341
+
342
+ @ Override
343
+ public boolean isWrapperFor (Class <?> iface ) throws SQLException {
344
+ return (iface .isInstance (this ) || this .dataSource .isWrapperFor (iface ));
345
+ }
346
+
347
+ }
348
+
206
349
}
0 commit comments