Skip to content

Commit 8cdbbdd

Browse files
committed
Merge pull request #15206 from ttddyy
* pr/15206: Polish "Perform best effort to retrieve DataSourceProxy" Perform best effort to retrieve DataSourceProxy
2 parents 44632ea + e424dfb commit 8cdbbdd

File tree

5 files changed

+312
-5
lines changed

5 files changed

+312
-5
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
3232
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
3333
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
34+
import org.springframework.boot.jdbc.DataSourceUnwrapper;
3435
import org.springframework.context.annotation.Bean;
3536
import org.springframework.context.annotation.Configuration;
3637
import org.springframework.jmx.export.MBeanExporter;
@@ -89,9 +90,11 @@ static class TomcatDataSourceJmxConfiguration {
8990
@Bean
9091
@ConditionalOnMissingBean(name = "dataSourceMBean")
9192
public Object dataSourceMBean(DataSource dataSource) {
92-
if (dataSource instanceof DataSourceProxy) {
93+
DataSourceProxy dataSourceProxy = DataSourceUnwrapper.unwrap(dataSource,
94+
DataSourceProxy.class);
95+
if (dataSourceProxy != null) {
9396
try {
94-
return ((DataSourceProxy) dataSource).createPool().getJmxPool();
97+
return dataSourceProxy.createPool().getJmxPool();
9598
}
9699
catch (SQLException ex) {
97100
logger.warn("Cannot expose DataSource to JMX (could not connect)");

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

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,15 @@
3838
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
3939
import org.springframework.context.annotation.Bean;
4040
import org.springframework.context.annotation.Configuration;
41+
import org.springframework.jdbc.datasource.DelegatingDataSource;
4142

4243
import static org.assertj.core.api.Assertions.assertThat;
4344

4445
/**
4546
* Tests for {@link DataSourceJmxConfiguration}.
4647
*
4748
* @author Stephane Nicoll
49+
* @author Tadaya Tsuyukubo
4850
*/
4951
public class DataSourceJmxConfigurationTests {
5052

@@ -155,13 +157,40 @@ public void tomcatAutoConfiguredCanExposeMBeanPool() {
155157
this.contextRunner.withPropertyValues(
156158
"spring.datasource.type=" + DataSource.class.getName(),
157159
"spring.datasource.jmx-enabled=true").run((context) -> {
160+
assertThat(context).hasBean("dataSourceMBean");
158161
assertThat(context).hasSingleBean(ConnectionPool.class);
159162
assertThat(context.getBean(DataSourceProxy.class).createPool()
160163
.getJmxPool())
161164
.isSameAs(context.getBean(ConnectionPool.class));
162165
});
163166
}
164167

168+
@Test
169+
public void tomcatProxiedCanExposeMBeanPool() {
170+
this.contextRunner.withUserConfiguration(DataSourceProxyConfiguration.class)
171+
.withPropertyValues(
172+
"spring.datasource.type=" + DataSource.class.getName(),
173+
"spring.datasource.jmx-enabled=true")
174+
.run((context) -> {
175+
assertThat(context).hasBean("dataSourceMBean");
176+
assertThat(context).getBean("dataSourceMBean")
177+
.isInstanceOf(ConnectionPool.class);
178+
});
179+
}
180+
181+
@Test
182+
public void tomcatDelegateCanExposeMBeanPool() {
183+
this.contextRunner.withUserConfiguration(DataSourceDelegateConfiguration.class)
184+
.withPropertyValues(
185+
"spring.datasource.type=" + DataSource.class.getName(),
186+
"spring.datasource.jmx-enabled=true")
187+
.run((context) -> {
188+
assertThat(context).hasBean("dataSourceMBean");
189+
assertThat(context).getBean("dataSourceMBean")
190+
.isInstanceOf(ConnectionPool.class);
191+
});
192+
}
193+
165194
@Configuration
166195
static class DataSourceProxyConfiguration {
167196

@@ -177,13 +206,28 @@ private static class DataSourceBeanPostProcessor implements BeanPostProcessor {
177206
@Override
178207
public Object postProcessAfterInitialization(Object bean, String beanName) {
179208
if (bean instanceof javax.sql.DataSource) {
180-
return wrap((javax.sql.DataSource) bean);
209+
return new ProxyFactory(bean).getProxy();
181210
}
182211
return bean;
183212
}
184213

185-
private static javax.sql.DataSource wrap(javax.sql.DataSource dataSource) {
186-
return (javax.sql.DataSource) new ProxyFactory(dataSource).getProxy();
214+
}
215+
216+
@Configuration
217+
static class DataSourceDelegateConfiguration {
218+
219+
@Bean
220+
public static DataSourceBeanPostProcessor dataSourceBeanPostProcessor() {
221+
return new DataSourceBeanPostProcessor() {
222+
@Override
223+
public Object postProcessAfterInitialization(Object bean,
224+
String beanName) {
225+
if (bean instanceof javax.sql.DataSource) {
226+
return new DelegatingDataSource((javax.sql.DataSource) bean);
227+
}
228+
return bean;
229+
}
230+
};
187231
}
188232

189233
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.jdbc;
18+
19+
import java.sql.Wrapper;
20+
21+
import javax.sql.DataSource;
22+
23+
import org.springframework.aop.framework.AopProxyUtils;
24+
import org.springframework.aop.support.AopUtils;
25+
import org.springframework.jdbc.datasource.DelegatingDataSource;
26+
import org.springframework.util.ClassUtils;
27+
28+
/**
29+
* Unwraps a {@link DataSource} that may have been proxied or wrapped in a custom
30+
* {@link Wrapper} such as {@link DelegatingDataSource}.
31+
*
32+
* @author Tadaya Tsuyukubo
33+
* @author Stephane Nicoll
34+
* @since 2.0.7
35+
*/
36+
public final class DataSourceUnwrapper {
37+
38+
private static final boolean DELEGATING_DATA_SOURCE_PRESENT = ClassUtils.isPresent(
39+
"org.springframework.jdbc.datasource.DelegatingDataSource",
40+
DataSourceUnwrapper.class.getClassLoader());
41+
42+
private DataSourceUnwrapper() {
43+
}
44+
45+
/**
46+
* Return an object that implements the given {@code target} type, unwrapping delegate
47+
* or proxy if necessary.
48+
* @param dataSource the datasource to handle
49+
* @param target the type that the result must implement
50+
* @param <T> the target type
51+
* @return an object that implements the target type or {@code null}
52+
*/
53+
public static <T> T unwrap(DataSource dataSource, Class<T> target) {
54+
if (target.isInstance(dataSource)) {
55+
return target.cast(dataSource);
56+
}
57+
T unwrapped = safeUnwrap(dataSource, target);
58+
if (unwrapped != null) {
59+
return unwrapped;
60+
}
61+
if (DELEGATING_DATA_SOURCE_PRESENT) {
62+
DataSource targetDataSource = DelegatingDataSourceUnwrapper
63+
.getTargetDataSource(dataSource);
64+
if (targetDataSource != null) {
65+
return unwrap(targetDataSource, target);
66+
}
67+
}
68+
if (AopUtils.isAopProxy(dataSource)) {
69+
Object proxyTarget = AopProxyUtils.getSingletonTarget(dataSource);
70+
if (proxyTarget instanceof DataSource) {
71+
return unwrap((DataSource) proxyTarget, target);
72+
}
73+
}
74+
return null;
75+
}
76+
77+
private static <S> S safeUnwrap(Wrapper wrapper, Class<S> target) {
78+
try {
79+
return wrapper.unwrap(target);
80+
}
81+
catch (Exception ex) {
82+
return null;
83+
}
84+
}
85+
86+
private static class DelegatingDataSourceUnwrapper {
87+
88+
private static DataSource getTargetDataSource(DataSource dataSource) {
89+
if (dataSource instanceof DelegatingDataSource) {
90+
return ((DelegatingDataSource) dataSource).getTargetDataSource();
91+
}
92+
return null;
93+
}
94+
95+
}
96+
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.jdbc;
18+
19+
import javax.sql.DataSource;
20+
21+
import com.zaxxer.hikari.HikariDataSource;
22+
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
26+
import org.springframework.aop.framework.ProxyFactory;
27+
import org.springframework.boot.testsupport.runner.classpath.ClassPathExclusions;
28+
import org.springframework.boot.testsupport.runner.classpath.ModifiedClassPathRunner;
29+
30+
import static org.assertj.core.api.Assertions.assertThat;
31+
32+
/**
33+
* Integration tests for {@link DataSourceUnwrapper} when spring-jdbc is not available.
34+
*
35+
* @author Stephane Nicoll
36+
*/
37+
@RunWith(ModifiedClassPathRunner.class)
38+
@ClassPathExclusions("spring-jdbc-*.jar")
39+
public class DataSourceUnwrapperNoSpringJdbcTests {
40+
41+
@Test
42+
public void unwrapWithProxy() {
43+
DataSource dataSource = new HikariDataSource();
44+
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
45+
assertThat(DataSourceUnwrapper.unwrap(actual, HikariDataSource.class))
46+
.isSameAs(dataSource);
47+
}
48+
49+
@Test
50+
public void unwrapDataSourceProxy() {
51+
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
52+
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
53+
assertThat(DataSourceUnwrapper.unwrap(actual, DataSourceProxy.class))
54+
.isSameAs(dataSource);
55+
}
56+
57+
private DataSource wrapInProxy(DataSource dataSource) {
58+
return (DataSource) new ProxyFactory(dataSource).getProxy();
59+
}
60+
61+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Copyright 2012-2018 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.jdbc;
18+
19+
import javax.sql.DataSource;
20+
21+
import com.zaxxer.hikari.HikariDataSource;
22+
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
23+
import org.junit.Test;
24+
25+
import org.springframework.aop.framework.ProxyFactory;
26+
import org.springframework.jdbc.datasource.DelegatingDataSource;
27+
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
31+
/**
32+
* Tests for {@link DataSourceUnwrapper}.
33+
*
34+
* @author Stephane Nicoll
35+
*/
36+
public class DataSourceUnwrapperTests {
37+
38+
@Test
39+
public void unwrapWithTarget() {
40+
DataSource dataSource = new HikariDataSource();
41+
assertThat(DataSourceUnwrapper.unwrap(dataSource, HikariDataSource.class))
42+
.isSameAs(dataSource);
43+
}
44+
45+
@Test
46+
public void unwrapWithWrongTarget() {
47+
DataSource dataSource = new HikariDataSource();
48+
assertThat(
49+
DataSourceUnwrapper.unwrap(dataSource, SingleConnectionDataSource.class))
50+
.isNull();
51+
}
52+
53+
@Test
54+
public void unwrapWithDelegate() {
55+
DataSource dataSource = new HikariDataSource();
56+
DataSource actual = wrapInDelegate(wrapInDelegate(dataSource));
57+
assertThat(DataSourceUnwrapper.unwrap(actual, HikariDataSource.class))
58+
.isSameAs(dataSource);
59+
}
60+
61+
@Test
62+
public void unwrapWithProxy() {
63+
DataSource dataSource = new HikariDataSource();
64+
DataSource actual = wrapInProxy(wrapInProxy(dataSource));
65+
assertThat(DataSourceUnwrapper.unwrap(actual, HikariDataSource.class))
66+
.isSameAs(dataSource);
67+
}
68+
69+
@Test
70+
public void unwrapWithProxyAndDelegate() {
71+
DataSource dataSource = new HikariDataSource();
72+
DataSource actual = wrapInProxy(wrapInDelegate(dataSource));
73+
assertThat(DataSourceUnwrapper.unwrap(actual, HikariDataSource.class))
74+
.isSameAs(dataSource);
75+
}
76+
77+
@Test
78+
public void unwrapWithSeveralLevelOfWrapping() {
79+
DataSource dataSource = new HikariDataSource();
80+
DataSource actual = wrapInProxy(wrapInDelegate(
81+
wrapInDelegate((wrapInProxy(wrapInDelegate(dataSource))))));
82+
assertThat(DataSourceUnwrapper.unwrap(actual, HikariDataSource.class))
83+
.isSameAs(dataSource);
84+
}
85+
86+
@Test
87+
public void unwrapDataSourceProxy() {
88+
org.apache.tomcat.jdbc.pool.DataSource dataSource = new org.apache.tomcat.jdbc.pool.DataSource();
89+
DataSource actual = wrapInDelegate(wrapInProxy(dataSource));
90+
assertThat(DataSourceUnwrapper.unwrap(actual, DataSourceProxy.class))
91+
.isSameAs(dataSource);
92+
}
93+
94+
private DataSource wrapInProxy(DataSource dataSource) {
95+
return (DataSource) new ProxyFactory(dataSource).getProxy();
96+
}
97+
98+
private DataSource wrapInDelegate(DataSource dataSource) {
99+
return new DelegatingDataSource(dataSource);
100+
}
101+
102+
}

0 commit comments

Comments
 (0)