Commit f304d469 authored by Dave Syer's avatar Dave Syer

Add JMX MBean for Tomcat DataSource

If the DataSource is a Tomcat one we force it to register an MBean
if spring.jmx.enabled=true

Fixes gh-1590
parent 0061f237
...@@ -16,9 +16,14 @@ ...@@ -16,9 +16,14 @@
package org.springframework.boot.autoconfigure.jdbc; package org.springframework.boot.autoconfigure.jdbc;
import java.sql.SQLException;
import javax.sql.DataSource; import javax.sql.DataSource;
import javax.sql.XADataSource; import javax.sql.XADataSource;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.jdbc.pool.DataSourceProxy;
import org.springframework.beans.factory.BeanFactoryUtils; import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
...@@ -27,6 +32,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; ...@@ -27,6 +32,7 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome; import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition; import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar; import org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerPostProcessor.Registrar;
...@@ -60,6 +66,8 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType; ...@@ -60,6 +66,8 @@ import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
@Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class }) @Import({ Registrar.class, DataSourcePoolMetadataProvidersConfiguration.class })
public class DataSourceAutoConfiguration { public class DataSourceAutoConfiguration {
private static Log logger = LogFactory.getLog(DataSourceAutoConfiguration.class);
/** /**
* Determines if the {@code dataSource} being used by Spring was created from * Determines if the {@code dataSource} being used by Spring was created from
* {@link EmbeddedDataSourceConfiguration}. * {@link EmbeddedDataSourceConfiguration}.
...@@ -134,7 +142,25 @@ public class DataSourceAutoConfiguration { ...@@ -134,7 +142,25 @@ public class DataSourceAutoConfiguration {
public NamedParameterJdbcTemplate namedParameterJdbcTemplate() { public NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
return new NamedParameterJdbcTemplate(this.dataSource); return new NamedParameterJdbcTemplate(this.dataSource);
} }
}
@Configuration
@ConditionalOnExpression("${spring.datasource.jmxEnabled:true}")
@ConditionalOnClass(name = "org.apache.tomcat.jdbc.pool.DataSourceProxy")
@Conditional(DataSourceAutoConfiguration.DataSourceAvailableCondition.class)
protected static class TomcatDataSourceJmxConfiguration {
@Bean
public Object dataSourceMBean(DataSource dataSource) {
if (dataSource instanceof DataSourceProxy) {
try {
return ((DataSourceProxy) dataSource).createPool().getJmxPool();
}
catch (SQLException e) {
logger.warn("Cannot expose DataSource to JMX (could not connect)");
}
}
return null;
}
} }
/** /**
......
...@@ -26,6 +26,7 @@ import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport; ...@@ -26,6 +26,7 @@ import org.apache.tomcat.jdbc.pool.interceptor.SlowQueryReport;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.test.EnvironmentTestUtils; import org.springframework.boot.test.EnvironmentTestUtils;
import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext;
...@@ -134,11 +135,20 @@ public class TomcatDataSourceConfigurationTests { ...@@ -134,11 +135,20 @@ public class TomcatDataSourceConfigurationTests {
@Import(DataSourceAutoConfiguration.class) @Import(DataSourceAutoConfiguration.class)
protected static class TomcatDataSourceConfiguration { protected static class TomcatDataSourceConfiguration {
@Autowired
private DataSourceProperties properties;
@Bean @Bean
@ConfigurationProperties(prefix = DataSourceProperties.PREFIX) @ConfigurationProperties(prefix = DataSourceProperties.PREFIX)
public DataSource dataSource() { public DataSource dataSource() {
return DataSourceBuilder.create() DataSourceBuilder factory = DataSourceBuilder
.type(org.apache.tomcat.jdbc.pool.DataSource.class).build(); .create(this.properties.getClassLoader())
.driverClassName(this.properties.getDriverClassName())
.url(this.properties.getUrl())
.username(this.properties.getUsername())
.password(this.properties.getPassword())
.type(org.apache.tomcat.jdbc.pool.DataSource.class);
return factory.build();
} }
} }
......
package sample.data.jpa; package sample.data.jpa;
import static org.junit.Assert.assertEquals;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import java.lang.management.ManagementFactory;
import javax.management.ObjectName;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.SpringApplicationConfiguration; import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.WebApplicationContext;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/** /**
* Integration test to run the application. * Integration test to run the application.
* *
* @author Oliver Gierke * @author Oliver Gierke
* @author Dave Syer
*/ */
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SampleDataJpaApplication.class) @SpringApplicationConfiguration(classes = SampleDataJpaApplication.class)
@WebAppConfiguration @WebAppConfiguration
// Enable JMX so we can test the MBeans (you can't do this in a properties file)
@TestPropertySource(properties="spring.jmx.enabled:true")
@ActiveProfiles("scratch") @ActiveProfiles("scratch")
// Separate profile for web tests to avoid clashing databases // Separate profile for web tests to avoid clashing databases
public class SampleDataJpaApplicationTests { public class SampleDataJpaApplicationTests {
...@@ -44,4 +53,11 @@ public class SampleDataJpaApplicationTests { ...@@ -44,4 +53,11 @@ public class SampleDataJpaApplicationTests {
this.mvc.perform(get("/")).andExpect(status().isOk()) this.mvc.perform(get("/")).andExpect(status().isOk())
.andExpect(content().string("Bath")); .andExpect(content().string("Bath"));
} }
@Test
public void testJmx() throws Exception {
assertEquals(1, ManagementFactory.getPlatformMBeanServer().queryMBeans(
new ObjectName("jpa.sample:type=ConnectionPool,*"), null).size());
}
} }
spring.datasource.url: jdbc:hsqldb:mem:scratchdb spring.datasource.url: jdbc:hsqldb:mem:scratchdb
spring.jmx.default_domain: jpa.sample
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment