From 0f652cdf4f61ef0621d4754da01973648cafd2c8 Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Wed, 28 Feb 2018 14:40:31 +0100 Subject: [PATCH] Fix injection point with proxied DataSource Prior to this commit, the `ApplicationContext` couldn't start with a JDK-proxied `HikariDataSource` as the JMX auto-configuration was attempting to inject a (too narrowed) `HikariDataSource`. This commit rather injects a regular `DataSource` and attempt to unwrap it as a `HikariDataSource`. Closes gh-12271 --- .../jdbc/DataSourceJmxConfiguration.java | 24 ++++++--- .../jdbc/DataSourceJmxConfigurationTests.java | 49 +++++++++++++++++++ 2 files changed, 66 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java index 9453116f07..fb497ceb21 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -48,23 +48,33 @@ class DataSourceJmxConfiguration { @Configuration @ConditionalOnClass(HikariDataSource.class) - @ConditionalOnSingleCandidate(HikariDataSource.class) + @ConditionalOnSingleCandidate(DataSource.class) static class Hikari { - private final HikariDataSource dataSource; + private final DataSource dataSource; private final ObjectProvider mBeanExporter; - Hikari(HikariDataSource dataSource, ObjectProvider mBeanExporter) { + Hikari(DataSource dataSource, ObjectProvider mBeanExporter) { this.dataSource = dataSource; this.mBeanExporter = mBeanExporter; } @PostConstruct public void validateMBeans() { - MBeanExporter exporter = this.mBeanExporter.getIfUnique(); - if (exporter != null && this.dataSource.isRegisterMbeans()) { - exporter.addExcludedBean("dataSource"); + HikariDataSource hikariDataSource = unwrapHikariDataSource(); + if (hikariDataSource != null && hikariDataSource.isRegisterMbeans()) { + this.mBeanExporter.ifUnique((exporter) -> + exporter.addExcludedBean("dataSource")); + } + } + + private HikariDataSource unwrapHikariDataSource() { + try { + return this.dataSource.unwrap(HikariDataSource.class); + } + catch (SQLException ex) { + return null; } } diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java index f0a17dea80..19809e7125 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java @@ -31,9 +31,13 @@ import org.apache.tomcat.jdbc.pool.DataSourceProxy; import org.apache.tomcat.jdbc.pool.jmx.ConnectionPool; import org.junit.Test; +import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import static org.assertj.core.api.Assertions.assertThat; @@ -101,6 +105,24 @@ public class DataSourceJmxConfigurationTests { }); } + @Test + public void hikariProxiedCanUseRegisterMBeans() { + String poolName = UUID.randomUUID().toString(); + this.contextRunner.withUserConfiguration(DataSourceProxyConfiguration.class) + .withPropertyValues( + "spring.datasource.type=" + HikariDataSource.class.getName(), + "spring.datasource.name=" + poolName, + "spring.datasource.hikari.register-mbeans=true") + .run((context) -> { + assertThat(context).hasSingleBean(javax.sql.DataSource.class); + HikariDataSource hikariDataSource = context.getBean( + javax.sql.DataSource.class).unwrap(HikariDataSource.class); + assertThat(hikariDataSource.isRegisterMbeans()).isTrue(); + MBeanServer mBeanServer = context.getBean(MBeanServer.class); + validateHikariMBeansRegistration(mBeanServer, poolName, true); + }); + } + private void validateHikariMBeansRegistration(MBeanServer mBeanServer, String poolName, boolean expected) throws MalformedObjectNameException { assertThat(mBeanServer.isRegistered( @@ -130,4 +152,31 @@ public class DataSourceJmxConfigurationTests { }); } + @Configuration + static class DataSourceProxyConfiguration { + + @Bean + public static DataSourceBeanPostProcessor dataSourceBeanPostProcessor() { + return new DataSourceBeanPostProcessor(); + } + + } + + + private static class DataSourceBeanPostProcessor implements BeanPostProcessor { + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) { + if (bean instanceof javax.sql.DataSource) { + return wrap((javax.sql.DataSource) bean); + } + return bean; + } + + private static javax.sql.DataSource wrap(javax.sql.DataSource dataSource) { + return (javax.sql.DataSource) new ProxyFactory(dataSource).getProxy(); + } + + } + }