diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java index d6f86e0197..352e64d3f2 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceAutoConfiguration.java @@ -106,7 +106,8 @@ public class DataSourceAutoConfiguration { @Conditional(PooledDataSourceCondition.class) @ConditionalOnMissingBean({ DataSource.class, XADataSource.class }) @Import({ DataSourceConfiguration.Hikari.class, DataSourceConfiguration.Tomcat.class, - DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class }) + DataSourceConfiguration.Dbcp2.class, DataSourceConfiguration.Generic.class, + DataSourceJmxConfiguration.class }) protected static class PooledDataSourceConfiguration { } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java new file mode 100644 index 0000000000..e8eb8f8775 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfiguration.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jdbc; + +import javax.annotation.PostConstruct; + +import com.zaxxer.hikari.HikariDataSource; + +import org.springframework.beans.factory.ObjectProvider; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate; +import org.springframework.context.annotation.Configuration; +import org.springframework.jmx.export.MBeanExporter; + +/** + * Configures DataSource related MBeans. + * + * @author Stephane Nicoll + */ +@Configuration +@ConditionalOnProperty(prefix = "spring.jmx", name = "enabled", havingValue = "true", matchIfMissing = true) +class DataSourceJmxConfiguration { + + @Configuration + @ConditionalOnClass(HikariDataSource.class) + @ConditionalOnSingleCandidate(HikariDataSource.class) + static class Hikari { + + private final HikariDataSource dataSource; + + private final ObjectProvider mBeanExporter; + + Hikari(HikariDataSource 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"); + } + } + + } + +} diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java new file mode 100644 index 0000000000..1bb102f784 --- /dev/null +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/jdbc/DataSourceJmxConfigurationTests.java @@ -0,0 +1,138 @@ +/* + * Copyright 2012-2017 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.autoconfigure.jdbc; + +import java.lang.management.ManagementFactory; +import java.util.UUID; + +import javax.management.MBeanServer; +import javax.management.MalformedObjectNameException; +import javax.management.ObjectName; + +import com.zaxxer.hikari.HikariDataSource; +import org.hsqldb.jdbc.JDBCDriver; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration; +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.jmx.export.UnableToRegisterMBeanException; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link DataSourceJmxConfiguration}. + * + * @author Stephane Nicoll + */ +public class DataSourceJmxConfigurationTests { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + private ConfigurableApplicationContext context; + + @After + public void close() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void hikariCustomCannotUseRegisterMBeansByDefault() { + this.thrown.expect(UnableToRegisterMBeanException.class); + this.thrown.expectMessage("myDataSource"); + load(CustomHikariConfiguration.class); + } + + @Test + public void hikariAutoConfiguredCanUseRegisterMBeans() + throws MalformedObjectNameException { + String poolName = UUID.randomUUID().toString(); + load("spring.datasource.type=" + HikariDataSource.class.getName(), + "spring.datasource.hikari.pool-name=" + poolName, + "spring.datasource.hikari.register-mbeans=true"); + assertThat(this.context.getBeansOfType(HikariDataSource.class)).hasSize(1); + assertThat(this.context.getBean(HikariDataSource.class).isRegisterMbeans()) + .isTrue(); + MBeanServer mBeanServer = this.context.getBean(MBeanServer.class); + validateHikariMBeansRegistration(mBeanServer, poolName, true); + } + + @Test + public void hikariAutoConfiguredUsesJmsFlag() throws MalformedObjectNameException { + String poolName = UUID.randomUUID().toString(); + load("spring.datasource.type=" + HikariDataSource.class.getName(), + "spring.jmx.enabled=false", + "spring.datasource.hikari.pool-name=" + poolName, + "spring.datasource.hikari.register-mbeans=true"); + assertThat(this.context.getBeansOfType(HikariDataSource.class)).hasSize(1); + assertThat(this.context.getBean(HikariDataSource.class).isRegisterMbeans()) + .isTrue(); + // Hikari can still register mBeans + validateHikariMBeansRegistration(ManagementFactory.getPlatformMBeanServer(), + poolName, true); + + } + + private void validateHikariMBeansRegistration(MBeanServer mBeanServer, + String poolName, boolean expected) throws MalformedObjectNameException { + assertThat(mBeanServer.isRegistered(new ObjectName( + "com.zaxxer.hikari:type=Pool (" + poolName + ")"))).isEqualTo(expected); + assertThat(mBeanServer.isRegistered(new ObjectName( + "com.zaxxer.hikari:type=PoolConfig (" + poolName + ")"))).isEqualTo(expected); + } + + private void load(String... environment) { + load(null, environment); + } + + private void load(Class config, String... environment) { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); + String jdbcUrl = "jdbc:hsqldb:mem:test-" + UUID.randomUUID().toString(); + TestPropertyValues.of(environment).and("spring.datasource.url", jdbcUrl) + .applyTo(ctx); + if (config != null) { + ctx.register(config); + } + ctx.register(JmxAutoConfiguration.class, DataSourceAutoConfiguration.class); + ctx.refresh(); + this.context = ctx; + } + + @Configuration + static class CustomHikariConfiguration { + + @Bean + public HikariDataSource myDataSource() { + HikariDataSource dataSource = new HikariDataSource(); + dataSource.setJdbcUrl("jdbc:hsqldb:mem:custom"); + dataSource.setDriverClassName(JDBCDriver.class.getName()); + dataSource.setRegisterMbeans(true); + return dataSource; + } + + } + +}