From 7f8a75a6fb37b56e4b163c42b61ad1760082ede5 Mon Sep 17 00:00:00 2001 From: LordKay-sudo Date: Mon, 29 Jun 2026 16:51:41 +0200 Subject: [PATCH] Prevent Micrometer global registry from pinning application contexts Track meter registries added to Metrics.globalRegistry and remove them on context close. Disable use of the global registry in tests by default to avoid pinning cached test contexts. See gh-50886 Signed-off-by: LordKay-sudo --- ...MeterRegistryContextCustomizerFactory.java | 75 +++++++++++++++++++ .../boot/test/metrics/package-info.java | 23 ++++++ .../main/resources/META-INF/spring.factories | 3 +- ...RegistryContextCustomizerFactoryTests.java | 56 ++++++++++++++ .../MeterRegistryPostProcessor.java | 17 +++-- .../MetricsAutoConfiguration.java | 21 ++++-- .../MeterRegistryPostProcessorTests.java | 68 +++++++++++++---- 7 files changed, 236 insertions(+), 27 deletions(-) create mode 100644 core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactory.java create mode 100644 core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/package-info.java create mode 100644 core/spring-boot-test/src/test/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactoryTests.java diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactory.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactory.java new file mode 100644 index 00000000000..d69c5da9860 --- /dev/null +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactory.java @@ -0,0 +1,75 @@ +/* + * Copyright 2012-present 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 + * + * https://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.test.metrics; + +import java.util.List; + +import org.jspecify.annotations.Nullable; + +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; +import org.springframework.test.context.ContextConfigurationAttributes; +import org.springframework.test.context.ContextCustomizer; +import org.springframework.test.context.ContextCustomizerFactory; +import org.springframework.test.context.MergedContextConfiguration; +import org.springframework.util.ClassUtils; + +/** + * {@link ContextCustomizerFactory} to disable the use of Micrometer's + * {@link io.micrometer.core.instrument.Metrics#globalRegistry global registry} in tests, + * preventing {@link io.micrometer.core.instrument.MeterRegistry meter registries} from + * pinning application contexts when many test contexts are cached. + */ +class DisableGlobalMeterRegistryContextCustomizerFactory implements ContextCustomizerFactory { + + private static final String METRICS_CLASS = "io.micrometer.core.instrument.Metrics"; + + private static final String USE_GLOBAL_REGISTRY_PROPERTY = "management.metrics.use-global-registry"; + + @Override + public @Nullable ContextCustomizer createContextCustomizer(Class testClass, + List configAttributes) { + if (ClassUtils.isPresent(METRICS_CLASS, testClass.getClassLoader())) { + return new DisableGlobalMeterRegistryContextCustomizer(); + } + return null; + } + + static final class DisableGlobalMeterRegistryContextCustomizer implements ContextCustomizer { + + @Override + public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) { + ConfigurableEnvironment environment = context.getEnvironment(); + if (environment.getProperty(USE_GLOBAL_REGISTRY_PROPERTY) == null) { + TestPropertyValues.of(USE_GLOBAL_REGISTRY_PROPERTY + "=false").applyTo(environment); + } + } + + @Override + public boolean equals(@Nullable Object obj) { + return obj instanceof DisableGlobalMeterRegistryContextCustomizer; + } + + @Override + public int hashCode() { + return getClass().hashCode(); + } + + } + +} diff --git a/core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/package-info.java b/core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/package-info.java new file mode 100644 index 00000000000..4d2792dd73c --- /dev/null +++ b/core/spring-boot-test/src/main/java/org/springframework/boot/test/metrics/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012-present 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 + * + * https://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. + */ + +/** + * Spring Boot support for metrics testing. + */ +@NullMarked +package org.springframework.boot.test.metrics; + +import org.jspecify.annotations.NullMarked; diff --git a/core/spring-boot-test/src/main/resources/META-INF/spring.factories b/core/spring-boot-test/src/main/resources/META-INF/spring.factories index 8a9b2c7d141..69d1c39b321 100644 --- a/core/spring-boot-test/src/main/resources/META-INF/spring.factories +++ b/core/spring-boot-test/src/main/resources/META-INF/spring.factories @@ -5,7 +5,8 @@ org.springframework.boot.test.context.PropertyMappingContextCustomizerFactory,\ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\ org.springframework.boot.test.context.filter.annotation.TypeExcludeFiltersContextCustomizerFactory,\ org.springframework.boot.test.http.client.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory,\ -org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory +org.springframework.boot.test.json.DuplicateJsonObjectContextCustomizerFactory,\ +org.springframework.boot.test.metrics.DisableGlobalMeterRegistryContextCustomizerFactory # Application Context Initializers org.springframework.context.ApplicationContextInitializer=\ diff --git a/core/spring-boot-test/src/test/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactoryTests.java b/core/spring-boot-test/src/test/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactoryTests.java new file mode 100644 index 00000000000..fd11852f4bc --- /dev/null +++ b/core/spring-boot-test/src/test/java/org/springframework/boot/test/metrics/DisableGlobalMeterRegistryContextCustomizerFactoryTests.java @@ -0,0 +1,56 @@ +/* + * Copyright 2012-present 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 + * + * https://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.test.metrics; + +import org.junit.jupiter.api.Test; +import org.springframework.test.context.MergedContextConfiguration; + +import org.springframework.boot.test.util.TestPropertyValues; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.core.env.StandardEnvironment; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link DisableGlobalMeterRegistryContextCustomizerFactory}. + */ +class DisableGlobalMeterRegistryContextCustomizerFactoryTests { + + @Test + void disablesGlobalMeterRegistryByDefault() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + context.setEnvironment(new StandardEnvironment()); + new DisableGlobalMeterRegistryContextCustomizerFactory.DisableGlobalMeterRegistryContextCustomizer() + .customizeContext(context, mock(MergedContextConfiguration.class)); + assertThat(context.getEnvironment().getProperty("management.metrics.use-global-registry")) + .isEqualTo("false"); + } + + @Test + void doesNotOverrideExplicitProperty() { + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); + StandardEnvironment environment = new StandardEnvironment(); + TestPropertyValues.of("management.metrics.use-global-registry=true").applyTo(environment); + context.setEnvironment(environment); + new DisableGlobalMeterRegistryContextCustomizerFactory.DisableGlobalMeterRegistryContextCustomizer() + .customizeContext(context, mock(MergedContextConfiguration.class)); + assertThat(context.getEnvironment().getProperty("management.metrics.use-global-registry")) + .isEqualTo("true"); + } + +} diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessor.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessor.java index e3be67195b3..e90937278a1 100644 --- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessor.java +++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessor.java @@ -54,25 +54,31 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing private final ObjectProvider binders; + private final ObjectProvider meterRegistryCloser; + private volatile boolean deferBinding = true; private final Set deferredBindings = new LinkedHashSet<>(); MeterRegistryPostProcessor(ApplicationContext applicationContext, - ObjectProvider metricsProperties, ObjectProvider> customizers, - ObjectProvider filters, ObjectProvider binders) { - this(CompositeMeterRegistries.of(applicationContext), metricsProperties, customizers, filters, binders); + ObjectProvider metricsProperties, + ObjectProvider> customizers, ObjectProvider filters, + ObjectProvider binders, + ObjectProvider meterRegistryCloser) { + this(CompositeMeterRegistries.of(applicationContext), metricsProperties, customizers, filters, binders, + meterRegistryCloser); } MeterRegistryPostProcessor(CompositeMeterRegistries compositeMeterRegistries, ObjectProvider properties, ObjectProvider> customizers, - ObjectProvider filters, ObjectProvider binders) { + ObjectProvider filters, ObjectProvider binders, + ObjectProvider meterRegistryCloser) { this.compositeMeterRegistries = compositeMeterRegistries; this.properties = properties; this.customizers = customizers; this.filters = filters; this.binders = binders; - + this.meterRegistryCloser = meterRegistryCloser; } @Override @@ -123,6 +129,7 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing private void addToGlobalRegistryIfNecessary(MeterRegistry meterRegistry) { if (this.properties.getObject().isUseGlobalRegistry() && !isGlobalRegistry(meterRegistry)) { Metrics.addRegistry(meterRegistry); + this.meterRegistryCloser.getObject().track(meterRegistry); } } diff --git a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.java b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.java index 7a4d06b7fee..7143faf724a 100644 --- a/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.java +++ b/module/spring-boot-micrometer-metrics/src/main/java/org/springframework/boot/micrometer/metrics/autoconfigure/MetricsAutoConfiguration.java @@ -16,6 +16,9 @@ package org.springframework.boot.micrometer.metrics.autoconfigure; +import java.util.LinkedHashSet; +import java.util.Set; + import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.MeterRegistry; @@ -65,9 +68,10 @@ public final class MetricsAutoConfiguration { static MeterRegistryPostProcessor meterRegistryPostProcessor(ApplicationContext applicationContext, ObjectProvider metricsProperties, ObjectProvider> meterRegistryCustomizers, - ObjectProvider meterFilters, ObjectProvider meterBinders) { + ObjectProvider meterFilters, ObjectProvider meterBinders, + ObjectProvider meterRegistryCloser) { return new MeterRegistryPostProcessor(applicationContext, metricsProperties, meterRegistryCustomizers, - meterFilters, meterBinders); + meterFilters, meterBinders, meterRegistryCloser); } @Bean @@ -102,20 +106,25 @@ public final class MetricsAutoConfiguration { private final ApplicationContext context; - private final Iterable meterRegistries; - private final boolean useGlobalRegistry; + private final Set trackedRegistries = new LinkedHashSet<>(); + MeterRegistryCloser(ApplicationContext context, boolean useGlobalRegistry) { - this.meterRegistries = context.getBeansOfType(MeterRegistry.class).values(); this.context = context; this.useGlobalRegistry = useGlobalRegistry; } + void track(MeterRegistry meterRegistry) { + this.trackedRegistries.add(meterRegistry); + } + @Override public void onApplicationEvent(ContextClosedEvent event) { if (this.context.equals(event.getApplicationContext())) { - for (MeterRegistry meterRegistry : this.meterRegistries) { + Set meterRegistries = new LinkedHashSet<>(this.trackedRegistries); + meterRegistries.addAll(this.context.getBeansOfType(MeterRegistry.class).values()); + for (MeterRegistry meterRegistry : meterRegistries) { if (this.useGlobalRegistry) { Metrics.globalRegistry.remove(meterRegistry); } diff --git a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessorTests.java b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessorTests.java index 0cf3e28d21c..18bc9402cb3 100644 --- a/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessorTests.java +++ b/module/spring-boot-micrometer-metrics/src/test/java/org/springframework/boot/micrometer/metrics/autoconfigure/MeterRegistryPostProcessorTests.java @@ -39,7 +39,9 @@ import org.springframework.beans.BeansException; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.micrometer.metrics.MaximumAllowableTagsMeterFilter; +import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration.MeterRegistryCloser; import org.springframework.boot.micrometer.metrics.autoconfigure.MeterRegistryPostProcessor.CompositeMeterRegistries; +import org.springframework.context.ApplicationContext; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.BDDMockito.given; @@ -84,8 +86,12 @@ class MeterRegistryPostProcessorTests { @SuppressWarnings("NullAway.Init") private Config mockConfig; + private final MetricsAutoConfiguration.MeterRegistryCloser meterRegistryCloser; + MeterRegistryPostProcessorTests() { this.properties.setUseGlobalRegistry(false); + this.meterRegistryCloser = new MetricsAutoConfiguration.MeterRegistryCloser( + mock(ApplicationContext.class), true); } @Test @@ -94,7 +100,7 @@ class MeterRegistryPostProcessorTests { MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), - createObjectProvider(this.binders)); + createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); CompositeMeterRegistry composite = new CompositeMeterRegistry(); postProcessAndInitialize(processor, composite); then(this.mockCustomizer).should().customize(composite); @@ -105,7 +111,7 @@ class MeterRegistryPostProcessorTests { this.customizers.add(this.mockCustomizer); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createEmptyObjectProvider(), createObjectProvider(this.binders)); + createEmptyObjectProvider(), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, Collections.emptyList()); postProcessAndInitialize(processor, composite); @@ -118,7 +124,7 @@ class MeterRegistryPostProcessorTests { this.customizers.add(this.mockCustomizer); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); then(this.mockCustomizer).should().customize(this.mockRegistry); } @@ -129,7 +135,7 @@ class MeterRegistryPostProcessorTests { this.filters.add(this.mockFilter); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); then(this.mockConfig).should().meterFilter(this.mockFilter); } @@ -141,7 +147,7 @@ class MeterRegistryPostProcessorTests { this.filters.add(onlyOnceFilter); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, Collections.emptyList()); postProcessAndInitialize(processor, composite); @@ -156,7 +162,7 @@ class MeterRegistryPostProcessorTests { this.binders.add(this.mockBinder); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); then(this.mockBinder).should().bindTo(this.mockRegistry); } @@ -167,7 +173,7 @@ class MeterRegistryPostProcessorTests { MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), - createObjectProvider(this.binders)); + createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); CompositeMeterRegistry composite = new CompositeMeterRegistry(); postProcessAndInitialize(processor, composite); then(this.mockBinder).should().bindTo(composite); @@ -179,7 +185,7 @@ class MeterRegistryPostProcessorTests { MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.filters), - createEmptyObjectProvider()); + createEmptyObjectProvider(), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); then(this.mockBinder).shouldHaveNoInteractions(); } @@ -189,7 +195,7 @@ class MeterRegistryPostProcessorTests { this.binders.add(this.mockBinder); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createEmptyObjectProvider(), createObjectProvider(this.binders)); + createEmptyObjectProvider(), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, Collections.emptyList()); postProcessAndInitialize(processor, composite); @@ -201,7 +207,7 @@ class MeterRegistryPostProcessorTests { this.binders.add(this.mockBinder); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createEmptyObjectProvider()); + createObjectProvider(this.filters), createEmptyObjectProvider(), createMeterRegistryCloserProvider(this.meterRegistryCloser)); CompositeMeterRegistry composite = new CompositeMeterRegistry(); postProcessAndInitialize(processor, composite); then(this.mockBinder).shouldHaveNoInteractions(); @@ -212,7 +218,7 @@ class MeterRegistryPostProcessorTests { given(this.mockRegistry.config()).willReturn(this.mockConfig); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createEmptyObjectProvider()); + createObjectProvider(this.filters), createEmptyObjectProvider(), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); then(this.mockBinder).shouldHaveNoInteractions(); } @@ -225,7 +231,7 @@ class MeterRegistryPostProcessorTests { this.binders.add(this.mockBinder); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); InOrder ordered = inOrder(this.mockBinder, this.mockConfig, this.mockCustomizer); then(this.mockCustomizer).should(ordered).customize(this.mockRegistry); @@ -233,13 +239,29 @@ class MeterRegistryPostProcessorTests { then(this.mockBinder).should(ordered).bindTo(this.mockRegistry); } + @Test + void trackedRegistryIsRemovedFromGlobalRegistryOnContextClosedEvent() { + ApplicationContext applicationContext = mock(ApplicationContext.class); + MetricsAutoConfiguration.MeterRegistryCloser closer = new MetricsAutoConfiguration.MeterRegistryCloser( + applicationContext, true); + try { + Metrics.addRegistry(this.mockRegistry); + closer.track(this.mockRegistry); + closer.onApplicationEvent(new org.springframework.context.event.ContextClosedEvent(applicationContext)); + assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(this.mockRegistry); + } + finally { + Metrics.removeRegistry(this.mockRegistry); + } + } + @Test void postProcessAndInitializeWhenUseGlobalRegistryTrueAddsToGlobalRegistry() { given(this.mockRegistry.config()).willReturn(this.mockConfig); this.properties.setUseGlobalRegistry(true); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); try { postProcessAndInitialize(processor, this.mockRegistry); assertThat(Metrics.globalRegistry.getRegistries()).contains(this.mockRegistry); @@ -254,7 +276,7 @@ class MeterRegistryPostProcessorTests { given(this.mockRegistry.config()).willReturn(this.mockConfig); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); postProcessAndInitialize(processor, this.mockRegistry); assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(this.mockRegistry); } @@ -265,7 +287,7 @@ class MeterRegistryPostProcessorTests { this.binders.add(this.mockBinder); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, createObjectProvider(this.properties), createObjectProvider(this.customizers), - createObjectProvider(this.filters), createObjectProvider(this.binders)); + createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); processor.postProcessAfterInitialization(this.mockRegistry, "meterRegistry"); then(this.mockBinder).shouldHaveNoInteractions(); processor.afterSingletonsInstantiated(); @@ -307,4 +329,20 @@ class MeterRegistryPostProcessorTests { }; } + private ObjectProvider createMeterRegistryCloserProvider(MeterRegistryCloser closer) { + return new ObjectProvider<>() { + + @Override + public MeterRegistryCloser getObject() { + return closer; + } + + @Override + public Stream orderedStream() { + return Stream.of(closer); + } + + }; + } + }