Include non-default candidates in metrics and health

Previously, when Actuator expected to find multiple beans of the same
type, it used Map<String, Type> to inject them. Unfortunately, this
does not include beans that are not default candidates and there's
no way to request that autowiring includes such beans with Map-based
injection.

This commit switches from Map-based injection to querying the bean
factory for the desired beans. This is done using
SimpleAutowireCandidateResolver's new helper method,
resolveAutowireCandidates, that returns a Map<String, Type> of
beans including those that are not default candidates but excluding
those that are not autowire candidates.

Closes gh-43481
This commit is contained in:
Andy Wilkinson 2025-02-05 16:05:19 +00:00
parent f6f0daa47d
commit 4cb9d816b9
31 changed files with 400 additions and 179 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,9 +16,8 @@
package org.springframework.boot.actuate.autoconfigure.amqp;
import java.util.Map;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.amqp.RabbitHealthIndicator;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
@ -50,8 +49,8 @@ public class RabbitHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "rabbitHealthIndicator", "rabbitHealthContributor" })
public HealthContributor rabbitHealthContributor(Map<String, RabbitTemplate> rabbitTemplates) {
return createContributor(rabbitTemplates);
public HealthContributor rabbitHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, RabbitTemplate.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -16,8 +16,8 @@
package org.springframework.boot.actuate.autoconfigure.cache;
import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnAvailableEndpoint;
import org.springframework.boot.actuate.autoconfigure.endpoint.expose.EndpointExposure;
import org.springframework.boot.actuate.cache.CachesEndpoint;
@ -45,8 +45,9 @@ public class CachesEndpointAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public CachesEndpoint cachesEndpoint(Map<String, CacheManager> cacheManagers) {
return new CachesEndpoint(cacheManagers);
public CachesEndpoint cachesEndpoint(ConfigurableListableBeanFactory beanFactory) {
return new CachesEndpoint(
SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, CacheManager.class));
}
@Bean

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -20,6 +20,7 @@ import java.util.Map;
import com.datastax.oss.driver.api.core.CqlSession;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.cassandra.CassandraDriverHealthIndicator;
@ -49,8 +50,8 @@ class CassandraHealthContributorConfigurations {
@Bean
@ConditionalOnMissingBean(name = { "cassandraHealthIndicator", "cassandraHealthContributor" })
HealthContributor cassandraHealthContributor(Map<String, CqlSession> sessions) {
return createContributor(sessions);
HealthContributor cassandraHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, CqlSession.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.couchbase;
import java.util.Map;
import com.couchbase.client.java.Cluster;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.couchbase.CouchbaseHealthIndicator;
@ -55,8 +54,8 @@ public class CouchbaseHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "couchbaseHealthIndicator", "couchbaseHealthContributor" })
public HealthContributor couchbaseHealthContributor(Map<String, Cluster> clusters) {
return createContributor(clusters);
public HealthContributor couchbaseHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, Cluster.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,11 +16,10 @@
package org.springframework.boot.actuate.autoconfigure.couchbase;
import java.util.Map;
import com.couchbase.client.java.Cluster;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.couchbase.CouchbaseReactiveHealthIndicator;
@ -54,8 +53,8 @@ public class CouchbaseReactiveHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "couchbaseHealthIndicator", "couchbaseHealthContributor" })
public ReactiveHealthContributor couchbaseHealthContributor(Map<String, Cluster> clusters) {
return createContributor(clusters);
public ReactiveHealthContributor couchbaseHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, Cluster.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.data.elasticsearch;
import java.util.Map;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.data.elasticsearch.ElasticsearchReactiveHealthIndicator;
@ -54,8 +53,8 @@ public class ElasticsearchReactiveHealthContributorAutoConfiguration extends
@Bean
@ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" })
public ReactiveHealthContributor elasticsearchHealthContributor(Map<String, ReactiveElasticsearchClient> clients) {
return createContributor(clients);
public ReactiveHealthContributor elasticsearchHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, ReactiveElasticsearchClient.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,8 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.data.mongo;
import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.data.mongo.MongoHealthIndicator;
@ -52,8 +51,8 @@ public class MongoHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "mongoHealthIndicator", "mongoHealthContributor" })
public HealthContributor mongoHealthContributor(Map<String, MongoTemplate> mongoTemplates) {
return createContributor(mongoTemplates);
public HealthContributor mongoHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, MongoTemplate.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.data.mongo;
import java.util.Map;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.data.mongo.MongoReactiveHealthIndicator;
@ -53,8 +52,8 @@ public class MongoReactiveHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "mongoHealthIndicator", "mongoHealthContributor" })
public ReactiveHealthContributor mongoHealthContributor(Map<String, ReactiveMongoTemplate> reactiveMongoTemplates) {
return createContributor(reactiveMongoTemplates);
public ReactiveHealthContributor mongoHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, ReactiveMongoTemplate.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,8 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.data.redis;
import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.data.redis.RedisHealthIndicator;
@ -53,8 +52,8 @@ public class RedisHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "redisHealthIndicator", "redisHealthContributor" })
public HealthContributor redisHealthContributor(Map<String, RedisConnectionFactory> redisConnectionFactories) {
return createContributor(redisConnectionFactories);
public HealthContributor redisHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, RedisConnectionFactory.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -20,6 +20,7 @@ import java.util.Map;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.data.redis.RedisReactiveHealthIndicator;
@ -50,18 +51,15 @@ import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;
public class RedisReactiveHealthContributorAutoConfiguration extends
CompositeReactiveHealthContributorConfiguration<RedisReactiveHealthIndicator, ReactiveRedisConnectionFactory> {
private final Map<String, ReactiveRedisConnectionFactory> redisConnectionFactories;
RedisReactiveHealthContributorAutoConfiguration(
Map<String, ReactiveRedisConnectionFactory> redisConnectionFactories) {
super(RedisReactiveHealthIndicator::new);
this.redisConnectionFactories = redisConnectionFactories;
}
@Bean
@ConditionalOnMissingBean(name = { "redisHealthIndicator", "redisHealthContributor" })
public ReactiveHealthContributor redisHealthContributor() {
return createContributor(this.redisConnectionFactories);
public ReactiveHealthContributor redisHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, ReactiveRedisConnectionFactory.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.elasticsearch;
import java.util.Map;
import org.elasticsearch.client.RestClient;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.elasticsearch.ElasticsearchRestClientHealthIndicator;
@ -52,8 +51,8 @@ public class ElasticsearchRestHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "elasticsearchHealthIndicator", "elasticsearchHealthContributor" })
public HealthContributor elasticsearchHealthContributor(Map<String, RestClient> clients) {
return createContributor(clients);
public HealthContributor elasticsearchHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, RestClient.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.hazelcast;
import java.util.Map;
import com.hazelcast.core.HazelcastInstance;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.hazelcast.HazelcastHealthIndicator;
@ -52,8 +51,8 @@ public class HazelcastHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "hazelcastHealthIndicator", "hazelcastHealthContributor" })
public HealthContributor hazelcastHealthContributor(Map<String, HazelcastInstance> hazelcastInstances) {
return createContributor(hazelcastInstances);
public HealthContributor hazelcastHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, HazelcastInstance.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -19,6 +19,8 @@ package org.springframework.boot.actuate.autoconfigure.health;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.util.Assert;
/**
@ -46,6 +48,18 @@ public abstract class AbstractCompositeHealthContributorConfiguration<C, I exten
this.indicatorFactory = indicatorFactory;
}
/**
* Creates a composite contributor from the beans of the given {@code beanType}
* retrieved from the given {@code beanFactory}.
* @param beanFactory the bean factory from which the beans are retrieved
* @param beanType the type of the beans that are retrieved
* @return the contributor
* @since 3.4.3
*/
protected final C createContributor(ConfigurableListableBeanFactory beanFactory, Class<B> beanType) {
return createContributor(SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, beanType));
}
protected final C createContributor(Map<String, B> beans) {
Assert.notEmpty(beans, "Beans must not be empty");
if (beans.size() == 1) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -28,6 +28,8 @@ import javax.sql.DataSource;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.CompositeHealthContributor;
import org.springframework.boot.actuate.health.HealthContributor;
@ -84,8 +86,10 @@ public class DataSourceHealthContributorAutoConfiguration implements Initializin
@Bean
@ConditionalOnMissingBean(name = { "dbHealthIndicator", "dbHealthContributor" })
public HealthContributor dbHealthContributor(Map<String, DataSource> dataSources,
public HealthContributor dbHealthContributor(ConfigurableListableBeanFactory beanFactory,
DataSourceHealthIndicatorProperties dataSourceHealthIndicatorProperties) {
Map<String, DataSource> dataSources = SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory,
DataSource.class);
if (dataSourceHealthIndicatorProperties.isIgnoreRoutingDataSources()) {
Map<String, DataSource> filteredDatasources = dataSources.entrySet()
.stream()

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.jms;
import java.util.Map;
import jakarta.jms.ConnectionFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.HealthContributor;
@ -52,8 +51,8 @@ public class JmsHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "jmsHealthIndicator", "jmsHealthContributor" })
public HealthContributor jmsHealthContributor(Map<String, ConnectionFactory> connectionFactories) {
return createContributor(connectionFactories);
public HealthContributor jmsHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, ConnectionFactory.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,8 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.ldap;
import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.HealthContributor;
@ -51,8 +50,8 @@ public class LdapHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "ldapHealthIndicator", "ldapHealthContributor" })
public HealthContributor ldapHealthContributor(Map<String, LdapOperations> ldapOperations) {
return createContributor(ldapOperations);
public HealthContributor ldapHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, LdapOperations.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,8 +16,7 @@
package org.springframework.boot.actuate.autoconfigure.mail;
import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.HealthContributor;
@ -50,8 +49,8 @@ public class MailHealthContributorAutoConfiguration
@Bean
@ConditionalOnMissingBean(name = { "mailHealthIndicator", "mailHealthContributor" })
public HealthContributor mailHealthContributor(Map<String, JavaMailSenderImpl> mailSenders) {
return createContributor(mailSenders);
public HealthContributor mailHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, JavaMailSenderImpl.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -22,6 +22,8 @@ import java.util.Map;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.actuate.metrics.cache.CacheMeterBinderProvider;
import org.springframework.boot.actuate.metrics.cache.CacheMetricsRegistrar;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -50,9 +52,9 @@ class CacheMetricsRegistrarConfiguration {
private final Map<String, CacheManager> cacheManagers;
CacheMetricsRegistrarConfiguration(MeterRegistry registry, Collection<CacheMeterBinderProvider<?>> binderProviders,
Map<String, CacheManager> cacheManagers) {
ConfigurableListableBeanFactory beanFactory) {
this.registry = registry;
this.cacheManagers = cacheManagers;
this.cacheManagers = SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, CacheManager.class);
this.cacheMetricsRegistrar = new CacheMetricsRegistrar(this.registry, binderProviders);
bindCachesToRegistry();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -32,6 +32,8 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.jdbc.DataSourcePoolMetrics;
@ -67,9 +69,11 @@ public class DataSourcePoolMetricsAutoConfiguration {
private static final String DATASOURCE_SUFFIX = "dataSource";
@Bean
DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(Map<String, DataSource> dataSources,
DataSourcePoolMetadataMeterBinder dataSourcePoolMetadataMeterBinder(ConfigurableListableBeanFactory beanFactory,
ObjectProvider<DataSourcePoolMetadataProvider> metadataProviders) {
return new DataSourcePoolMetadataMeterBinder(dataSources, metadataProviders);
return new DataSourcePoolMetadataMeterBinder(
SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, DataSource.class),
metadataProviders);
}
static class DataSourcePoolMetadataMeterBinder implements MeterBinder {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -26,6 +26,8 @@ import org.hibernate.SessionFactory;
import org.hibernate.stat.HibernateMetrics;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
@ -57,9 +59,9 @@ public class HibernateMetricsAutoConfiguration implements SmartInitializingSingl
private final MeterRegistry meterRegistry;
public HibernateMetricsAutoConfiguration(Map<String, EntityManagerFactory> entityManagerFactories,
MeterRegistry meterRegistry) {
this.entityManagerFactories = entityManagerFactories;
public HibernateMetricsAutoConfiguration(ConfigurableListableBeanFactory beanFactory, MeterRegistry meterRegistry) {
this.entityManagerFactories = SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory,
EntityManagerFactory.class);
this.meterRegistry = meterRegistry;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,8 +16,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.r2dbc;
import java.util.Map;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tags;
import io.r2dbc.pool.ConnectionPool;
@ -25,6 +23,8 @@ import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.Wrapped;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
import org.springframework.boot.actuate.metrics.r2dbc.ConnectionPoolMetrics;
@ -49,14 +49,14 @@ import org.springframework.boot.autoconfigure.r2dbc.R2dbcAutoConfiguration;
public class ConnectionPoolMetricsAutoConfiguration {
@Autowired
public void bindConnectionPoolsToRegistry(Map<String, ConnectionFactory> connectionFactories,
MeterRegistry registry) {
connectionFactories.forEach((beanName, connectionFactory) -> {
ConnectionPool pool = extractPool(connectionFactory);
if (pool != null) {
new ConnectionPoolMetrics(pool, beanName, Tags.empty()).bindTo(registry);
}
});
public void bindConnectionPoolsToRegistry(ConfigurableListableBeanFactory beanFactory, MeterRegistry registry) {
SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, ConnectionFactory.class)
.forEach((beanName, connectionFactory) -> {
ConnectionPool pool = extractPool(connectionFactory);
if (pool != null) {
new ConnectionPoolMetrics(pool, beanName, Tags.empty()).bindTo(registry);
}
});
}
private ConnectionPool extractPool(Object candidate) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -17,7 +17,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.task;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@ -25,6 +24,8 @@ import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.binder.jvm.ExecutorServiceMetrics;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.SimpleAutowireCandidateResolver;
import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
@ -35,6 +36,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
@ -54,15 +56,16 @@ import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
public class TaskExecutorMetricsAutoConfiguration {
@Autowired
public void bindTaskExecutorsToRegistry(Map<String, Executor> executors, MeterRegistry registry) {
executors.forEach((beanName, executor) -> {
if (executor instanceof ThreadPoolTaskExecutor threadPoolTaskExecutor) {
monitor(registry, safeGetThreadPoolExecutor(threadPoolTaskExecutor), beanName);
}
else if (executor instanceof ThreadPoolTaskScheduler threadPoolTaskScheduler) {
monitor(registry, safeGetThreadPoolExecutor(threadPoolTaskScheduler), beanName);
}
});
public void bindTaskExecutorsToRegistry(ConfigurableListableBeanFactory beanFactory, MeterRegistry registry) {
SimpleAutowireCandidateResolver.resolveAutowireCandidates(beanFactory, TaskExecutor.class)
.forEach((beanName, executor) -> {
if (executor instanceof ThreadPoolTaskExecutor threadPoolTaskExecutor) {
monitor(registry, safeGetThreadPoolExecutor(threadPoolTaskExecutor), beanName);
}
else if (executor instanceof ThreadPoolTaskScheduler threadPoolTaskScheduler) {
monitor(registry, safeGetThreadPoolExecutor(threadPoolTaskScheduler), beanName);
}
});
}
@Bean

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,11 +16,10 @@
package org.springframework.boot.actuate.autoconfigure.neo4j;
import java.util.Map;
import org.neo4j.driver.Driver;
import reactor.core.publisher.Flux;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.health.HealthContributor;
@ -49,8 +48,8 @@ class Neo4jHealthContributorConfigurations {
@Bean
@ConditionalOnMissingBean(name = { "neo4jHealthIndicator", "neo4jHealthContributor" })
HealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
return createContributor(drivers);
HealthContributor neo4jHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, Driver.class);
}
}
@ -66,8 +65,8 @@ class Neo4jHealthContributorConfigurations {
@Bean
@ConditionalOnMissingBean(name = { "neo4jHealthIndicator", "neo4jHealthContributor" })
ReactiveHealthContributor neo4jHealthContributor(Map<String, Driver> drivers) {
return createContributor(drivers);
ReactiveHealthContributor neo4jHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, Driver.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2022 the original author or authors.
* Copyright 2012-2025 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.
@ -16,10 +16,9 @@
package org.springframework.boot.actuate.autoconfigure.r2dbc;
import java.util.Map;
import io.r2dbc.spi.ConnectionFactory;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.health.CompositeReactiveHealthContributorConfiguration;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.health.ReactiveHealthContributor;
@ -46,17 +45,14 @@ import org.springframework.context.annotation.Bean;
public class ConnectionFactoryHealthContributorAutoConfiguration
extends CompositeReactiveHealthContributorConfiguration<ConnectionFactoryHealthIndicator, ConnectionFactory> {
private final Map<String, ConnectionFactory> connectionFactory;
ConnectionFactoryHealthContributorAutoConfiguration(Map<String, ConnectionFactory> connectionFactory) {
ConnectionFactoryHealthContributorAutoConfiguration() {
super(ConnectionFactoryHealthIndicator::new);
this.connectionFactory = connectionFactory;
}
@Bean
@ConditionalOnMissingBean(name = { "r2dbcHealthIndicator", "r2dbcHealthContributor" })
public ReactiveHealthContributor r2dbcHealthContributor() {
return createContributor(this.connectionFactory);
public ReactiveHealthContributor r2dbcHealthContributor(ConfigurableListableBeanFactory beanFactory) {
return createContributor(beanFactory, ConnectionFactory.class);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -23,6 +23,11 @@ import java.util.Map;
import io.lettuce.core.dynamic.support.ResolvableType;
import org.junit.jupiter.api.Test;
import org.springframework.boot.actuate.health.NamedContributor;
import org.springframework.boot.actuate.health.NamedContributors;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.ClassUtils;
import static org.assertj.core.api.Assertions.assertThat;
@ -69,10 +74,80 @@ abstract class AbstractCompositeHealthContributorConfigurationTests<C, I extends
assertThat(ClassUtils.getShortName(contributor.getClass())).startsWith("Composite");
}
@Test
void createContributorWhenBeanFactoryHasNoBeansThrowsException() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.refresh();
assertThatIllegalArgumentException()
.isThrownBy(() -> newComposite().createContributor(context.getBeanFactory(), TestBean.class));
}
}
@Test
void createContributorWhenBeanFactoryHasSingleBeanCreatesIndicator() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(SingleBeanConfiguration.class);
context.refresh();
C contributor = newComposite().createContributor(context.getBeanFactory(), TestBean.class);
assertThat(contributor).isInstanceOf(this.indicatorType);
}
}
@Test
void createContributorWhenBeanFactoryHasMultipleBeansCreatesComposite() {
try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext()) {
context.register(MultipleBeansConfiguration.class);
context.refresh();
C contributor = newComposite().createContributor(context.getBeanFactory(), TestBean.class);
assertThat(contributor).isNotInstanceOf(this.indicatorType);
assertThat(ClassUtils.getShortName(contributor.getClass())).startsWith("Composite");
assertThat(((NamedContributors<?>) contributor).stream().map(NamedContributor::getName))
.containsExactlyInAnyOrder("standard", "nonDefault");
}
}
protected abstract AbstractCompositeHealthContributorConfiguration<C, I, TestBean> newComposite();
static class TestBean {
}
@Configuration(proxyBeanMethods = false)
static class MultipleBeansConfiguration {
@Bean
TestBean standard() {
return new TestBean();
}
@Bean(defaultCandidate = false)
TestBean nonDefault() {
return new TestBean();
}
@Bean(autowireCandidate = false)
TestBean nonAutowire() {
return new TestBean();
}
}
@Configuration(proxyBeanMethods = false)
static class SingleBeanConfiguration {
@Bean
TestBean standard() {
return new TestBean();
}
@Bean(autowireCandidate = false)
TestBean nonAutowire() {
return new TestBean();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -70,12 +70,14 @@ class DataSourceHealthContributorAutoConfigurationTests {
@Test
void runWhenMultipleDataSourceBeansShouldCreateCompositeIndicator() {
this.contextRunner.withUserConfiguration(EmbeddedDataSourceConfiguration.class, DataSourceConfig.class)
this.contextRunner
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, DataSourceConfig.class,
NonStandardDataSourceConfig.class)
.run((context) -> {
assertThat(context).hasSingleBean(CompositeHealthContributor.class);
CompositeHealthContributor contributor = context.getBean(CompositeHealthContributor.class);
String[] names = contributor.stream().map(NamedContributor::getName).toArray(String[]::new);
assertThat(names).containsExactlyInAnyOrder("dataSource", "testDataSource");
assertThat(names).containsExactlyInAnyOrder("dataSource", "standardDataSource", "nonDefaultDataSource");
});
}
@ -217,7 +219,7 @@ class DataSourceHealthContributorAutoConfigurationTests {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.test")
DataSource testDataSource() {
DataSource standardDataSource() {
return DataSourceBuilder.create()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
@ -228,6 +230,34 @@ class DataSourceHealthContributorAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
static class NonStandardDataSourceConfig {
@Bean(defaultCandidate = false)
@ConfigurationProperties(prefix = "spring.datasource.non-default")
DataSource nonDefaultDataSource() {
return DataSourceBuilder.create()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.url("jdbc:hsqldb:mem:non-default")
.username("sa")
.build();
}
@Bean(autowireCandidate = false)
@ConfigurationProperties(prefix = "spring.datasource.non-autowire")
DataSource nonAutowireDataSource() {
return DataSourceBuilder.create()
.type(org.apache.tomcat.jdbc.pool.DataSource.class)
.driverClassName("org.hsqldb.jdbc.JDBCDriver")
.url("jdbc:hsqldb:mem:non-autowire")
.username("sa")
.build();
}
}
@Configuration(proxyBeanMethods = false)
static class RoutingDataSourceConfig {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2024 the original author or authors.
* Copyright 2012-2025 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.
@ -16,6 +16,10 @@
package org.springframework.boot.actuate.autoconfigure.metrics.cache;
import java.util.Collections;
import java.util.List;
import com.github.benmanes.caffeine.cache.CaffeineSpec;
import io.micrometer.core.instrument.MeterRegistry;
import org.junit.jupiter.api.Test;
@ -23,7 +27,12 @@ import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.cache.interceptor.CacheResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import static org.assertj.core.api.Assertions.assertThat;
@ -91,6 +100,54 @@ class CacheMetricsAutoConfigurationTests {
});
}
@Test
void customCacheManagersAreInstrumented() {
this.contextRunner
.withPropertyValues("spring.cache.type=caffeine", "spring.cache.cache-names=cache1,cache2",
"spring.cache.caffeine.spec=recordStats")
.withUserConfiguration(CustomCacheManagersConfiguration.class)
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("cache.gets").meters()).map((meter) -> meter.getId().getTag("cache"))
.containsOnly("standard", "non-default");
});
}
@Configuration(proxyBeanMethods = false)
static class CustomCacheManagersConfiguration implements CachingConfigurer {
@Bean
CacheManager standardCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeineSpec(CaffeineSpec.parse("recordStats"));
cacheManager.setCacheNames(List.of("standard"));
return cacheManager;
}
@Bean(defaultCandidate = false)
CacheManager nonDefaultCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeineSpec(CaffeineSpec.parse("recordStats"));
cacheManager.setCacheNames(List.of("non-default"));
return cacheManager;
}
@Bean(autowireCandidate = false)
CacheManager nonAutowireCacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeineSpec(CaffeineSpec.parse("recordStats"));
cacheManager.setCacheNames(List.of("non-autowire"));
return cacheManager;
}
@Bean
@Override
public CacheResolver cacheResolver() {
return (context) -> Collections.emptyList();
}
}
@Configuration(proxyBeanMethods = false)
@EnableCaching
static class CachingConfiguration {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -86,11 +86,11 @@ class DataSourcePoolMetricsAutoConfigurationTests {
this.contextRunner.withConfiguration(AutoConfigurations.of(DataSourceAutoConfiguration.class))
.withUserConfiguration(TwoDataSourcesConfiguration.class)
.run((context) -> {
context.getBean("firstDataSource", DataSource.class).getConnection().getMetaData();
context.getBean("secondOne", DataSource.class).getConnection().getMetaData();
context.getBean("nonDefaultDataSource", DataSource.class).getConnection().getMetaData();
context.getBean("nonAutowireDataSource", DataSource.class).getConnection().getMetaData();
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("jdbc.connections.max").tags("name", "first").meter();
registry.get("jdbc.connections.max").tags("name", "secondOne").meter();
assertThat(registry.find("jdbc.connections.max").meters()).map((meter) -> meter.getId().getTag("name"))
.containsOnly("dataSource", "nonDefault");
});
}
@ -101,11 +101,11 @@ class DataSourcePoolMetricsAutoConfigurationTests {
(context) -> context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor()))
.withUserConfiguration(TwoDataSourcesConfiguration.class)
.run((context) -> {
context.getBean("firstDataSource", DataSource.class).getConnection().getMetaData();
context.getBean("secondOne", DataSource.class).getConnection().getMetaData();
context.getBean("nonDefaultDataSource", DataSource.class).getConnection().getMetaData();
context.getBean("nonAutowireDataSource", DataSource.class).getConnection().getMetaData();
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("jdbc.connections.max").tags("name", "first").meter();
registry.get("jdbc.connections.max").tags("name", "secondOne").meter();
assertThat(registry.find("jdbc.connections.max").meters()).map((meter) -> meter.getId().getTag("name"))
.containsOnly("dataSource", "nonDefault");
});
}
@ -239,13 +239,13 @@ class DataSourcePoolMetricsAutoConfigurationTests {
@Configuration(proxyBeanMethods = false)
static class TwoDataSourcesConfiguration {
@Bean
DataSource firstDataSource() {
@Bean(defaultCandidate = false)
DataSource nonDefaultDataSource() {
return createDataSource();
}
@Bean
DataSource secondOne() {
@Bean(autowireCandidate = false)
DataSource nonAutowireDataSource() {
return createDataSource();
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -101,13 +101,15 @@ class HibernateMetricsAutoConfigurationTests {
@Test
void allEntityManagerFactoriesCanBeInstrumented() {
this.contextRunner.withPropertyValues("spring.jpa.properties.hibernate.generate_statistics:true")
.withUserConfiguration(TwoEntityManagerFactoriesConfiguration.class)
.withUserConfiguration(MultipleEntityManagerFactoriesConfiguration.class)
.run((context) -> {
context.getBean("firstEntityManagerFactory", EntityManagerFactory.class).unwrap(SessionFactory.class);
context.getBean("secondOne", EntityManagerFactory.class).unwrap(SessionFactory.class);
context.getBean("nonDefault", EntityManagerFactory.class).unwrap(SessionFactory.class);
context.getBean("nonAutowire", EntityManagerFactory.class).unwrap(SessionFactory.class);
MeterRegistry registry = context.getBean(MeterRegistry.class);
registry.get("hibernate.statements").tags("entityManagerFactory", "first").meter();
registry.get("hibernate.statements").tags("entityManagerFactory", "secondOne").meter();
assertThat(registry.find("hibernate.statements").meters())
.map((meter) -> meter.getId().getTag("entityManagerFactory"))
.containsOnly("first", "nonDefault");
});
}
@ -171,7 +173,7 @@ class HibernateMetricsAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TwoEntityManagerFactoriesConfiguration {
static class MultipleEntityManagerFactoriesConfiguration {
private static final Class<?>[] PACKAGE_CLASSES = new Class<?>[] { MyEntity.class };
@ -181,8 +183,13 @@ class HibernateMetricsAutoConfigurationTests {
return createSessionFactory(ds);
}
@Bean
LocalContainerEntityManagerFactoryBean secondOne(DataSource ds) {
@Bean(defaultCandidate = false)
LocalContainerEntityManagerFactoryBean nonDefault(DataSource ds) {
return createSessionFactory(ds);
}
@Bean(autowireCandidate = false)
LocalContainerEntityManagerFactoryBean nonAutowire(DataSource ds) {
return createSessionFactory(ds);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -109,11 +109,10 @@ class ConnectionPoolMetricsAutoConfigurationTests {
@Test
void allConnectionPoolsCanBeInstrumented() {
this.contextRunner.withUserConfiguration(TwoConnectionPoolsConfiguration.class).run((context) -> {
this.contextRunner.withUserConfiguration(MultipleConnectionPoolsConfiguration.class).run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
assertThat(registry.find("r2dbc.pool.acquired").gauges()).extracting(Meter::getId)
.extracting((id) -> id.getTag("name"))
.containsExactlyInAnyOrder("firstPool", "secondPool");
assertThat(registry.find("r2dbc.pool.acquired").meters()).map((meter) -> meter.getId().getTag("name"))
.containsOnly("standardPool", "nonDefaultPool");
});
}
@ -179,7 +178,7 @@ class ConnectionPoolMetricsAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class TwoConnectionPoolsConfiguration {
static class MultipleConnectionPoolsConfiguration {
@Bean
CloseableConnectionFactory connectionFactory() {
@ -188,12 +187,17 @@ class ConnectionPoolMetricsAutoConfigurationTests {
}
@Bean
ConnectionPool firstPool(ConnectionFactory connectionFactory) {
ConnectionPool standardPool(ConnectionFactory connectionFactory) {
return new ConnectionPool(ConnectionPoolConfiguration.builder(connectionFactory).build());
}
@Bean
ConnectionPool secondPool(ConnectionFactory connectionFactory) {
@Bean(defaultCandidate = false)
ConnectionPool nonDefaultPool(ConnectionFactory connectionFactory) {
return new ConnectionPool(ConnectionPoolConfiguration.builder(connectionFactory).build());
}
@Bean(autowireCandidate = false)
ConnectionPool nonAutowirePool(ConnectionFactory connectionFactory) {
return new ConnectionPool(ConnectionPoolConfiguration.builder(connectionFactory).build());
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2023 the original author or authors.
* Copyright 2012-2025 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.
@ -17,7 +17,6 @@
package org.springframework.boot.actuate.autoconfigure.metrics.task;
import java.util.Collection;
import java.util.concurrent.Executor;
import io.micrometer.core.instrument.FunctionCounter;
import io.micrometer.core.instrument.MeterRegistry;
@ -30,6 +29,7 @@ import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration;
import org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration;
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@ -82,14 +82,12 @@ class TaskExecutorMetricsAutoConfigurationTests {
@Test
void taskExecutorsWithCustomNamesAreInstrumented() {
this.contextRunner.withBean("firstTaskExecutor", Executor.class, ThreadPoolTaskExecutor::new)
.withBean("customName", ThreadPoolTaskExecutor.class, ThreadPoolTaskExecutor::new)
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
assertThat(meters).map((meter) -> meter.getId().getTag("name"))
.containsExactlyInAnyOrder("firstTaskExecutor", "customName");
});
this.contextRunner.withUserConfiguration(MultipleTaskExecutorsConfiguration.class).run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
assertThat(meters).map((meter) -> meter.getId().getTag("name"))
.containsExactlyInAnyOrder("standardTaskExecutor", "nonDefault");
});
}
@Test
@ -134,14 +132,12 @@ class TaskExecutorMetricsAutoConfigurationTests {
@Test
void taskSchedulersWithCustomNamesAreInstrumented() {
this.contextRunner.withBean("firstTaskScheduler", Executor.class, ThreadPoolTaskScheduler::new)
.withBean("customName", ThreadPoolTaskScheduler.class, ThreadPoolTaskScheduler::new)
.run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
assertThat(meters).map((meter) -> meter.getId().getTag("name"))
.containsExactlyInAnyOrder("firstTaskScheduler", "customName");
});
this.contextRunner.withUserConfiguration(MultipleTaskSchedulersConfiguration.class).run((context) -> {
MeterRegistry registry = context.getBean(MeterRegistry.class);
Collection<FunctionCounter> meters = registry.get("executor.completed").functionCounters();
assertThat(meters).map((meter) -> meter.getId().getTag("name"))
.containsExactlyInAnyOrder("standardTaskScheduler", "nonDefault");
});
}
@Test
@ -176,4 +172,44 @@ class TaskExecutorMetricsAutoConfigurationTests {
}
@Configuration(proxyBeanMethods = false)
static class MultipleTaskSchedulersConfiguration {
@Bean
ThreadPoolTaskScheduler standardTaskScheduler() {
return new ThreadPoolTaskScheduler();
}
@Bean(defaultCandidate = false)
ThreadPoolTaskScheduler nonDefault() {
return new ThreadPoolTaskScheduler();
}
@Bean(autowireCandidate = false)
ThreadPoolTaskScheduler nonAutowire() {
return new ThreadPoolTaskScheduler();
}
}
@Configuration(proxyBeanMethods = false)
static class MultipleTaskExecutorsConfiguration {
@Bean
ThreadPoolTaskExecutor standardTaskExecutor() {
return new ThreadPoolTaskExecutor();
}
@Bean(defaultCandidate = false)
ThreadPoolTaskExecutor nonDefault() {
return new ThreadPoolTaskExecutor();
}
@Bean(autowireCandidate = false)
ThreadPoolTaskExecutor nonAutowire() {
return new ThreadPoolTaskExecutor();
}
}
}