From e237390e66f4cfe49fb9051673c6509757e4327c Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Tue, 18 Mar 2025 18:04:45 +0000 Subject: [PATCH] Ignore prototype DataSource beans for metrics and health Previously, if a prototype DataSource bean was defined, Actuator's metrics and health would try to access an instance of it. At best this was wasteful as the new instance would only be used for metrics and health and would not be indicative of the app's DataSource usage. At worst, it could cause a failure in the unusual case of the prototype bean definition requiring arguments to be supplied using ObjectProvider.getObject(Object...) or the like. This commit address the problem by ignoring prototype DataSource for metrics and health. Other types of beans for which Actuator provides metrics and health are similarly affected. They have not be fixed here as the situation is so unusual. Should another problem arise in the future, it can be addressed at that time when there's a clear need. Closes gh-44706 --- ...rceHealthContributorAutoConfiguration.java | 2 +- ...ataSourcePoolMetricsAutoConfiguration.java | 7 ++-- ...althContributorAutoConfigurationTests.java | 36 ++++++++++++++++++ ...urcePoolMetricsAutoConfigurationTests.java | 37 +++++++++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java index f6d2ee0786..379eb09e36 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfiguration.java @@ -89,7 +89,7 @@ public class DataSourceHealthContributorAutoConfiguration implements Initializin public HealthContributor dbHealthContributor(ConfigurableListableBeanFactory beanFactory, DataSourceHealthIndicatorProperties dataSourceHealthIndicatorProperties) { Map dataSources = SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, - DataSource.class); + DataSource.class, false, true); if (dataSourceHealthIndicatorProperties.isIgnoreRoutingDataSources()) { Map filteredDatasources = dataSources.entrySet() .stream() diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java index 8af299bf52..e6893be93e 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfiguration.java @@ -72,9 +72,8 @@ public class DataSourcePoolMetricsAutoConfiguration { @Bean DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(ConfigurableListableBeanFactory beanFactory, ObjectProvider metadataProviders) { - return new DataSourcePoolMetadataMeterBinder( - SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, DataSource.class), - metadataProviders); + return new DataSourcePoolMetadataMeterBinder(SimpleAutowireCandidateResolver + .resolveAutowireCandidates(beanFactory, DataSource.class, false, true), metadataProviders); } static class DataSourcePoolMetadataMeterBinder implements MeterBinder { @@ -141,7 +140,7 @@ public class DataSourcePoolMetricsAutoConfiguration { @Override public void bindTo(MeterRegistry registry) { - this.dataSources.stream(ObjectProvider.UNFILTERED).forEach((dataSource) -> { + this.dataSources.stream(ObjectProvider.UNFILTERED, false).forEach((dataSource) -> { HikariDataSource hikariDataSource = DataSourceUnwrapper.unwrap(dataSource, HikariConfigMXBean.class, HikariDataSource.class); if (hikariDataSource != null) { diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java index ab76ba5c1e..a5cfebe3a5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/jdbc/DataSourceHealthContributorAutoConfigurationTests.java @@ -19,12 +19,15 @@ package org.springframework.boot.actuate.autoconfigure.jdbc; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; +import java.util.UUID; import javax.sql.DataSource; +import com.zaxxer.hikari.HikariDataSource; import org.junit.jupiter.api.Test; import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.actuate.autoconfigure.health.HealthContributorAutoConfiguration; import org.springframework.boot.actuate.autoconfigure.jdbc.DataSourceHealthContributorAutoConfiguration.RoutingDataSourceHealthContributor; @@ -41,6 +44,7 @@ import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import static org.assertj.core.api.Assertions.assertThat; @@ -213,6 +217,16 @@ class DataSourceHealthContributorAutoConfigurationTests { }); } + @Test + void prototypeDataSourceIsIgnored() { + this.contextRunner + .withUserConfiguration(EmbeddedDataSourceConfiguration.class, PrototypeDataSourceConfiguration.class) + .run((context) -> { + assertThat(context).doesNotHaveBean(CompositeHealthContributor.class); + assertThat(context.getBeansOfType(DataSourceHealthIndicator.class)).hasSize(1); + }); + } + @Configuration(proxyBeanMethods = false) @EnableConfigurationProperties static class DataSourceConfig { @@ -317,4 +331,26 @@ class DataSourceHealthContributorAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class PrototypeDataSourceConfiguration { + + @Bean + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + DataSource dataSourcePrototype(String username, String password) { + return createHikariDataSource(username, password); + } + + private HikariDataSource createHikariDataSource(String username, String password) { + String url = "jdbc:hsqldb:mem:test-" + UUID.randomUUID(); + HikariDataSource hikariDataSource = DataSourceBuilder.create() + .url(url) + .type(HikariDataSource.class) + .username(username) + .password(password) + .build(); + return hikariDataSource; + } + + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java index 5e20efa842..7f43cc4fb8 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/jdbc/DataSourcePoolMetricsAutoConfigurationTests.java @@ -28,6 +28,7 @@ import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.jupiter.api.Test; import org.springframework.aop.framework.ProxyFactory; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.boot.LazyInitializationBeanFactoryPostProcessor; import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun; @@ -39,6 +40,7 @@ import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider; import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Scope; import org.springframework.core.Ordered; import org.springframework.core.PriorityOrdered; import org.springframework.jdbc.datasource.DelegatingDataSource; @@ -222,6 +224,19 @@ class DataSourcePoolMetricsAutoConfigurationTests { }); } + @Test + void prototypeDataSourceIsIgnored() { + this.contextRunner + .withUserConfiguration(OneHikariDataSourceConfiguration.class, PrototypeDataSourceConfiguration.class) + .run((context) -> { + context.getBean("hikariDataSource", DataSource.class).getConnection(); + ((DataSource) context.getBean("prototypeDataSource", "", "")).getConnection(); + MeterRegistry registry = context.getBean(MeterRegistry.class); + assertThat(registry.get("hikaricp.connections").meter().getId().getTags()) + .containsExactly(Tag.of("pool", "hikariDataSource")); + }); + } + private static HikariDataSource createHikariDataSource(String poolName) { String url = "jdbc:hsqldb:mem:test-" + UUID.randomUUID(); HikariDataSource hikariDataSource = DataSourceBuilder.create().url(url).type(HikariDataSource.class).build(); @@ -334,6 +349,28 @@ class DataSourcePoolMetricsAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class PrototypeDataSourceConfiguration { + + @Bean + @Scope(BeanDefinition.SCOPE_PROTOTYPE) + DataSource prototypeDataSource(String username, String password) { + return createHikariDataSource(username, password); + } + + private HikariDataSource createHikariDataSource(String username, String password) { + String url = "jdbc:hsqldb:mem:test-" + UUID.randomUUID(); + HikariDataSource hikariDataSource = DataSourceBuilder.create() + .url(url) + .type(HikariDataSource.class) + .username(username) + .password(password) + .build(); + return hikariDataSource; + } + + } + @Configuration(proxyBeanMethods = false) static class HikariSealingConfiguration {