Skip to content

Commit 26f9a92

Browse files
ttddyysnicoll
authored andcommitted
Perform best effort to retrieve DataSourceProxy
Prior to this commit, `DataSourceJmxConfiguration` with tomcat `DataSource`, it can only find `DataSourceProxy` if the given `DataSource` is a direct child of it. Since it uses `instanceof`, it could not find `DataSourceProxy` if the `DataSource` is wrapped(delegated) or proxied. This is because `DataSourceProxy#unwrap()` always returns null; thus cannot use this method to directly obtain `DataSourceProxy`. In this commit, updated the check logic to perform the best effort to retrieve `DataSourceProxy`. If given `DataSource` is wrapped or proxied by spring, tries to unwrap or get target datasource recursively to find `DataSourceProxy`. See gh-15206
1 parent 44632ea commit 26f9a92

File tree

2 files changed

+160
-4
lines changed

2 files changed

+160
-4
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,19 +26,23 @@
2626
import org.apache.commons.logging.LogFactory;
2727
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
2828

29+
import org.springframework.aop.framework.AopProxyUtils;
30+
import org.springframework.aop.support.AopUtils;
2931
import org.springframework.beans.factory.ObjectProvider;
3032
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3234
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3335
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
3436
import org.springframework.context.annotation.Bean;
3537
import org.springframework.context.annotation.Configuration;
38+
import org.springframework.jdbc.datasource.DelegatingDataSource;
3639
import org.springframework.jmx.export.MBeanExporter;
3740

3841
/**
3942
* Configures DataSource related MBeans.
4043
*
4144
* @author Stephane Nicoll
45+
* @author Tadaya Tsuyukubo
4246
*/
4347
@Configuration
4448
@ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true", matchIfMissing = true)
@@ -89,9 +93,10 @@ static class TomcatDataSourceJmxConfiguration {
8993
@Bean
9094
@ConditionalOnMissingBean(name = "dataSourceMBean")
9195
public Object dataSourceMBean(DataSource dataSource) {
92-
if (dataSource instanceof DataSourceProxy) {
96+
DataSourceProxy dataSourceProxy = extractDataSourceProxy(dataSource);
97+
if (dataSourceProxy != null) {
9398
try {
94-
return ((DataSourceProxy) dataSource).createPool().getJmxPool();
99+
return dataSourceProxy.createPool().getJmxPool();
95100
}
96101
catch (SQLException ex) {
97102
logger.warn("Cannot expose DataSource to JMX (could not connect)");
@@ -100,6 +105,36 @@ public Object dataSourceMBean(DataSource dataSource) {
100105
return null;
101106
}
102107

108+
/**
109+
* Perform best effort to retrieve tomcat's {@link DataSourceProxy}.
110+
*
111+
* Since {@link DataSourceProxy#unwrap(Class)} always return {@code null}, it
112+
* cannot directly retrieve {@link DataSourceProxy}. This method tries best effort
113+
* to find {@link DataSourceProxy} if the given {@link DataSource} is wrapped or
114+
* proxied by spring.
115+
* @param dataSource candidate datasource
116+
* @return found DataSourceProxy or null
117+
*/
118+
private DataSourceProxy extractDataSourceProxy(DataSource dataSource) {
119+
if (dataSource instanceof DataSourceProxy) {
120+
return (DataSourceProxy) dataSource; // found
121+
}
122+
else if (dataSource instanceof DelegatingDataSource) {
123+
// check delegating target
124+
return extractDataSourceProxy(
125+
((DelegatingDataSource) dataSource).getTargetDataSource());
126+
}
127+
else if (AopUtils.isAopProxy(dataSource)) {
128+
// for proxy by spring, try target(advised) instance
129+
Object target = AopProxyUtils.getSingletonTarget(dataSource);
130+
if (target instanceof DataSource) {
131+
return extractDataSourceProxy((DataSource) target);
132+
}
133+
}
134+
135+
return null;
136+
}
137+
103138
}
104139

105140
}

spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,24 @@
3131
import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool;
3232
import org.junit.Test;
3333

34+
import org.springframework.aop.framework.AopProxyUtils;
3435
import org.springframework.aop.framework.ProxyFactory;
36+
import org.springframework.aop.support.AopUtils;
3537
import org.springframework.beans.factory.config.BeanPostProcessor;
3638
import org.springframework.boot.autoconfigure.AutoConfigurations;
3739
import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration;
3840
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3941
import org.springframework.context.annotation.Bean;
4042
import org.springframework.context.annotation.Configuration;
43+
import org.springframework.jdbc.datasource.DelegatingDataSource;
4144

4245
import static org.assertj.core.api.Assertions.assertThat;
4346

4447
/**
4548
* Tests for {@link DataSourceJmxConfiguration}.
4649
*
4750
* @author Stephane Nicoll
51+
* @author Tadaya Tsuyukubo
4852
*/
4953
public class DataSourceJmxConfigurationTests {
5054

@@ -162,6 +166,78 @@ public void tomcatAutoConfiguredCanExposeMBeanPool() {
162166
});
163167
}
164168

169+
@Test
170+
public void tomcatProxiedCanExposeMBeanPool() {
171+
this.contextRunner.withUserConfiguration(DataSourceProxyConfiguration.class)
172+
.withPropertyValues(
173+
"spring.datasource.type=" + DataSource.class.getName(),
174+
"spring.datasource.jmx-enabled=true")
175+
.run((context) -> {
176+
assertThat(context).hasSingleBean(ConnectionPool.class);
177+
DataSourceProxy dataSourceProxy = (DataSourceProxy) AopProxyUtils
178+
.getSingletonTarget(
179+
context.getBean(javax.sql.DataSource.class));
180+
assertThat(dataSourceProxy.createPool().getJmxPool())
181+
.isSameAs(context.getBean(ConnectionPool.class));
182+
});
183+
}
184+
185+
@Test
186+
public void tomcatDelegateCanExposeMBeanPool() {
187+
this.contextRunner.withUserConfiguration(DataSourceDelegateConfiguration.class)
188+
.withPropertyValues(
189+
"spring.datasource.type=" + DataSource.class.getName(),
190+
"spring.datasource.jmx-enabled=true")
191+
.run((context) -> {
192+
assertThat(context).hasSingleBean(ConnectionPool.class);
193+
DataSourceProxy dataSourceProxy = (DataSourceProxy) context
194+
.getBean(DelegatingDataSource.class).getTargetDataSource();
195+
assertThat(dataSourceProxy.createPool().getJmxPool())
196+
.isSameAs(context.getBean(ConnectionPool.class));
197+
});
198+
}
199+
200+
@Test
201+
public void tomcatProxyAndDelegateCanExposeMBeanPool() {
202+
this.contextRunner
203+
.withUserConfiguration(DataSourceMixWrapAndProxyConfiguration.class)
204+
.withPropertyValues(
205+
"spring.datasource.type=" + DataSource.class.getName(),
206+
"spring.datasource.jmx-enabled=true")
207+
.run((context) -> {
208+
assertThat(context).hasSingleBean(ConnectionPool.class);
209+
DataSourceProxy dataSourceProxy = extractTomcatDataSource(
210+
context.getBean(javax.sql.DataSource.class));
211+
assertThat(dataSourceProxy.createPool().getJmxPool())
212+
.isSameAs(context.getBean(ConnectionPool.class));
213+
});
214+
}
215+
216+
private static javax.sql.DataSource wrap(javax.sql.DataSource dataSource) {
217+
return (javax.sql.DataSource) new ProxyFactory(dataSource).getProxy();
218+
}
219+
220+
private static javax.sql.DataSource delegate(javax.sql.DataSource dataSource) {
221+
return new DelegatingDataSource(dataSource);
222+
}
223+
224+
private static DataSource extractTomcatDataSource(javax.sql.DataSource dataSource) {
225+
if (dataSource instanceof DataSource) {
226+
return (DataSource) dataSource;
227+
}
228+
else if (dataSource instanceof DelegatingDataSource) {
229+
return extractTomcatDataSource(
230+
((DelegatingDataSource) dataSource).getTargetDataSource());
231+
}
232+
else if (AopUtils.isAopProxy(dataSource)) {
233+
return extractTomcatDataSource(
234+
(javax.sql.DataSource) AopProxyUtils.getSingletonTarget(dataSource));
235+
}
236+
237+
throw new RuntimeException(
238+
"Not proxied or delegated tomcat DataSource: " + dataSource);
239+
}
240+
165241
@Configuration
166242
static class DataSourceProxyConfiguration {
167243

@@ -182,8 +258,53 @@ public Object postProcessAfterInitialization(Object bean, String beanName) {
182258
return bean;
183259
}
184260

185-
private static javax.sql.DataSource wrap(javax.sql.DataSource dataSource) {
186-
return (javax.sql.DataSource) new ProxyFactory(dataSource).getProxy();
261+
}
262+
263+
@Configuration
264+
static class DataSourceDelegateConfiguration {
265+
266+
@Bean
267+
public static DataSourceBeanPostProcessor dataSourceBeanPostProcessor() {
268+
return new DataSourceBeanPostProcessor() {
269+
@Override
270+
public Object postProcessAfterInitialization(Object bean,
271+
String beanName) {
272+
if (bean instanceof javax.sql.DataSource) {
273+
return new DelegatingDataSource((javax.sql.DataSource) bean);
274+
}
275+
return bean;
276+
}
277+
};
278+
}
279+
280+
}
281+
282+
@Configuration
283+
static class DataSourceMixWrapAndProxyConfiguration {
284+
285+
@Bean
286+
public static DataSourceBeanPostProcessor dataSourceBeanPostProcessor() {
287+
return new DataSourceBeanPostProcessor() {
288+
@Override
289+
public Object postProcessAfterInitialization(Object bean,
290+
String beanName) {
291+
if (bean instanceof javax.sql.DataSource) {
292+
javax.sql.DataSource dataSource = (javax.sql.DataSource) bean;
293+
// delegate/wrap multiple times
294+
for (int i = 0; i < 10; i++) {
295+
if (i % 2 == 0) {
296+
dataSource = wrap(dataSource);
297+
}
298+
else {
299+
dataSource = delegate(dataSource);
300+
}
301+
}
302+
303+
return dataSource;
304+
}
305+
return bean;
306+
}
307+
};
187308
}
188309

189310
}

0 commit comments

Comments
 (0)