Polish "Prevent Micrometer global registry from pinning application contexts"

See gh-50886
This commit is contained in:
Moritz Halbritter
2026-07-28 10:14:06 +02:00
parent 7f8a75a6fb
commit 19153b69a4
10 changed files with 253 additions and 239 deletions
@@ -1,75 +0,0 @@
/*
* 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<ContextConfigurationAttributes> 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();
}
}
}
@@ -1,23 +0,0 @@
/*
* 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;
@@ -5,8 +5,7 @@ org.springframework.boot.test.context.PropertyMappingContextCustomizerFactory,\
org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\ org.springframework.boot.test.context.filter.ExcludeFilterContextCustomizerFactory,\
org.springframework.boot.test.context.filter.annotation.TypeExcludeFiltersContextCustomizerFactory,\ org.springframework.boot.test.context.filter.annotation.TypeExcludeFiltersContextCustomizerFactory,\
org.springframework.boot.test.http.client.DisableReactorResourceFactoryGlobalResourcesContextCustomizerFactory,\ 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 # Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\ org.springframework.context.ApplicationContextInitializer=\
@@ -1,56 +0,0 @@
/*
* 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");
}
}
@@ -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.micrometer.metrics.autoconfigure;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent;
/**
* Ensures that {@link MeterRegistry meter registries} are closed early in the shutdown
* process. Also unregisters the registries from the global registry if needed.
*
* @author Jon Schneider
* @author Stephane Nicoll
* @author Moritz Halbritter
* @author Michael Berry
* @author Phillip Webb
* @author Lordwill Kandiro
*/
class MeterRegistryCloser implements ApplicationListener<ContextClosedEvent> {
private final ApplicationContext context;
private final Set<MeterRegistry> registriesToClose = new CopyOnWriteArraySet<>();
private final Set<MeterRegistry> registriesToRemoveFromGlobalRegistry = new CopyOnWriteArraySet<>();
MeterRegistryCloser(ApplicationContext context) {
this.context = context;
}
void track(MeterRegistry meterRegistry) {
this.registriesToClose.add(meterRegistry);
}
void trackAddedToGlobalRegistry(MeterRegistry meterRegistry) {
this.registriesToRemoveFromGlobalRegistry.add(meterRegistry);
}
@Override
public void onApplicationEvent(ContextClosedEvent event) {
if (!this.context.equals(event.getApplicationContext())) {
return;
}
for (MeterRegistry meterRegistry : this.registriesToRemoveFromGlobalRegistry) {
Metrics.removeRegistry(meterRegistry);
}
for (MeterRegistry meterRegistry : this.registriesToClose) {
if (!meterRegistry.isClosed()) {
meterRegistry.close();
}
}
}
}
@@ -54,17 +54,16 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing
private final ObjectProvider<MeterBinder> binders; private final ObjectProvider<MeterBinder> binders;
private final ObjectProvider<MetricsAutoConfiguration.MeterRegistryCloser> meterRegistryCloser; private final ObjectProvider<MeterRegistryCloser> meterRegistryCloser;
private volatile boolean deferBinding = true; private volatile boolean deferBinding = true;
private final Set<MeterRegistry> deferredBindings = new LinkedHashSet<>(); private final Set<MeterRegistry> deferredBindings = new LinkedHashSet<>();
MeterRegistryPostProcessor(ApplicationContext applicationContext, MeterRegistryPostProcessor(ApplicationContext applicationContext,
ObjectProvider<MetricsProperties> metricsProperties, ObjectProvider<MetricsProperties> metricsProperties, ObjectProvider<MeterRegistryCustomizer<?>> customizers,
ObjectProvider<MeterRegistryCustomizer<?>> customizers, ObjectProvider<MeterFilter> filters, ObjectProvider<MeterFilter> filters, ObjectProvider<MeterBinder> binders,
ObjectProvider<MeterBinder> binders, ObjectProvider<MeterRegistryCloser> meterRegistryCloser) {
ObjectProvider<MetricsAutoConfiguration.MeterRegistryCloser> meterRegistryCloser) {
this(CompositeMeterRegistries.of(applicationContext), metricsProperties, customizers, filters, binders, this(CompositeMeterRegistries.of(applicationContext), metricsProperties, customizers, filters, binders,
meterRegistryCloser); meterRegistryCloser);
} }
@@ -72,7 +71,7 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing
MeterRegistryPostProcessor(CompositeMeterRegistries compositeMeterRegistries, MeterRegistryPostProcessor(CompositeMeterRegistries compositeMeterRegistries,
ObjectProvider<MetricsProperties> properties, ObjectProvider<MeterRegistryCustomizer<?>> customizers, ObjectProvider<MetricsProperties> properties, ObjectProvider<MeterRegistryCustomizer<?>> customizers,
ObjectProvider<MeterFilter> filters, ObjectProvider<MeterBinder> binders, ObjectProvider<MeterFilter> filters, ObjectProvider<MeterBinder> binders,
ObjectProvider<MetricsAutoConfiguration.MeterRegistryCloser> meterRegistryCloser) { ObjectProvider<MeterRegistryCloser> meterRegistryCloser) {
this.compositeMeterRegistries = compositeMeterRegistries; this.compositeMeterRegistries = compositeMeterRegistries;
this.properties = properties; this.properties = properties;
this.customizers = customizers; this.customizers = customizers;
@@ -98,6 +97,7 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing
} }
private void postProcessMeterRegistry(MeterRegistry meterRegistry) { private void postProcessMeterRegistry(MeterRegistry meterRegistry) {
this.meterRegistryCloser.getObject().track(meterRegistry);
// Customizers must be applied before binders, as they may add custom tags or // Customizers must be applied before binders, as they may add custom tags or
// alter timer or summary configuration. // alter timer or summary configuration.
applyCustomizers(meterRegistry); applyCustomizers(meterRegistry);
@@ -129,7 +129,7 @@ class MeterRegistryPostProcessor implements BeanPostProcessor, SmartInitializing
private void addToGlobalRegistryIfNecessary(MeterRegistry meterRegistry) { private void addToGlobalRegistryIfNecessary(MeterRegistry meterRegistry) {
if (this.properties.getObject().isUseGlobalRegistry() && !isGlobalRegistry(meterRegistry)) { if (this.properties.getObject().isUseGlobalRegistry() && !isGlobalRegistry(meterRegistry)) {
Metrics.addRegistry(meterRegistry); Metrics.addRegistry(meterRegistry);
this.meterRegistryCloser.getObject().track(meterRegistry); this.meterRegistryCloser.getObject().trackAddedToGlobalRegistry(meterRegistry);
} }
} }
@@ -16,13 +16,9 @@
package org.springframework.boot.micrometer.metrics.autoconfigure; 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.annotation.Timed;
import io.micrometer.core.instrument.Clock; import io.micrometer.core.instrument.Clock;
import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.MeterBinder;
import io.micrometer.core.instrument.composite.CompositeMeterRegistry; import io.micrometer.core.instrument.composite.CompositeMeterRegistry;
import io.micrometer.core.instrument.config.MeterFilter; import io.micrometer.core.instrument.config.MeterFilter;
@@ -38,9 +34,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.micrometer.observation.autoconfigure.ObservationHandlerGroup; import org.springframework.boot.micrometer.observation.autoconfigure.ObservationHandlerGroup;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextClosedEvent;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
/** /**
@@ -81,8 +75,8 @@ public final class MetricsAutoConfiguration {
} }
@Bean @Bean
MeterRegistryCloser meterRegistryCloser(ApplicationContext context, MetricsProperties properties) { MeterRegistryCloser meterRegistryCloser(ApplicationContext context) {
return new MeterRegistryCloser(context, properties.isUseGlobalRegistry()); return new MeterRegistryCloser(context);
} }
@Bean @Bean
@@ -98,43 +92,4 @@ public final class MetricsAutoConfiguration {
properties.getObservations().getIgnoredMeters().toArray(IgnoredMeters[]::new)); properties.getObservations().getIgnoredMeters().toArray(IgnoredMeters[]::new));
} }
/**
* Ensures that {@link MeterRegistry meter registries} are closed early in the
* shutdown process.
*/
static class MeterRegistryCloser implements ApplicationListener<ContextClosedEvent> {
private final ApplicationContext context;
private final boolean useGlobalRegistry;
private final Set<MeterRegistry> trackedRegistries = new LinkedHashSet<>();
MeterRegistryCloser(ApplicationContext context, boolean useGlobalRegistry) {
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())) {
Set<MeterRegistry> meterRegistries = new LinkedHashSet<>(this.trackedRegistries);
meterRegistries.addAll(this.context.getBeansOfType(MeterRegistry.class).values());
for (MeterRegistry meterRegistry : meterRegistries) {
if (this.useGlobalRegistry) {
Metrics.globalRegistry.remove(meterRegistry);
}
if (!meterRegistry.isClosed()) {
meterRegistry.close();
}
}
}
}
}
} }
@@ -0,0 +1,113 @@
/*
* 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.micrometer.metrics.autoconfigure;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.then;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
/**
* Tests for {@link MeterRegistryCloser}.
*
* @author Lordwill Kandiro
* @author Moritz Halbritter
*/
class MeterRegistryCloserTests {
private final ApplicationContext context = mock(ApplicationContext.class);
@Test
void trackedRegistryIsRemovedFromGlobalRegistryOnContextClosedEvent() {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
MeterRegistryCloser closer = new MeterRegistryCloser(this.context);
try {
Metrics.addRegistry(meterRegistry);
closer.trackAddedToGlobalRegistry(meterRegistry);
closer.onApplicationEvent(new ContextClosedEvent(this.context));
assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(meterRegistry);
}
finally {
Metrics.removeRegistry(meterRegistry);
}
}
@Test
void trackedRegistryIsClosedOnContextClosedEvent() {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
MeterRegistryCloser closer = new MeterRegistryCloser(this.context);
closer.track(meterRegistry);
closer.onApplicationEvent(new ContextClosedEvent(this.context));
assertThat(meterRegistry.isClosed()).isTrue();
}
@Test
void trackedRegistriesAreClosedInTheOrderThatTheyWereTracked() {
MeterRegistry first = mock(MeterRegistry.class);
MeterRegistry second = mock(MeterRegistry.class);
MeterRegistry third = mock(MeterRegistry.class);
MeterRegistryCloser closer = new MeterRegistryCloser(this.context);
closer.track(first);
closer.track(second);
closer.track(third);
closer.onApplicationEvent(new ContextClosedEvent(this.context));
InOrder ordered = inOrder(first, second, third);
then(first).should(ordered).close();
then(second).should(ordered).close();
then(third).should(ordered).close();
}
@Test
void alreadyClosedRegistryIsNotClosedAgain() {
MeterRegistry meterRegistry = mock(MeterRegistry.class);
given(meterRegistry.isClosed()).willReturn(true);
MeterRegistryCloser closer = new MeterRegistryCloser(this.context);
closer.track(meterRegistry);
closer.onApplicationEvent(new ContextClosedEvent(this.context));
then(meterRegistry).should(never()).close();
}
@Test
void onApplicationEventIgnoresEventsFromOtherContexts() {
MeterRegistry meterRegistry = new SimpleMeterRegistry();
MeterRegistryCloser closer = new MeterRegistryCloser(this.context);
try {
Metrics.addRegistry(meterRegistry);
closer.track(meterRegistry);
closer.trackAddedToGlobalRegistry(meterRegistry);
ApplicationContext otherContext = mock(ApplicationContext.class);
closer.onApplicationEvent(new ContextClosedEvent(otherContext));
assertThat(Metrics.globalRegistry.getRegistries()).contains(meterRegistry);
assertThat(meterRegistry.isClosed()).isFalse();
}
finally {
Metrics.removeRegistry(meterRegistry);
}
}
}
@@ -39,9 +39,9 @@ import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.micrometer.metrics.MaximumAllowableTagsMeterFilter; 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.boot.micrometer.metrics.autoconfigure.MeterRegistryPostProcessor.CompositeMeterRegistries;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.given;
@@ -86,12 +86,13 @@ class MeterRegistryPostProcessorTests {
@SuppressWarnings("NullAway.Init") @SuppressWarnings("NullAway.Init")
private Config mockConfig; private Config mockConfig;
private final MetricsAutoConfiguration.MeterRegistryCloser meterRegistryCloser; private final ApplicationContext meterRegistryCloserContext = mock(ApplicationContext.class);
private final MeterRegistryCloser meterRegistryCloser;
MeterRegistryPostProcessorTests() { MeterRegistryPostProcessorTests() {
this.properties.setUseGlobalRegistry(false); this.properties.setUseGlobalRegistry(false);
this.meterRegistryCloser = new MetricsAutoConfiguration.MeterRegistryCloser( this.meterRegistryCloser = new MeterRegistryCloser(this.meterRegistryCloserContext);
mock(ApplicationContext.class), true);
} }
@Test @Test
@@ -111,7 +112,8 @@ class MeterRegistryPostProcessorTests {
this.customizers.add(this.mockCustomizer); this.customizers.add(this.mockCustomizer);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createEmptyObjectProvider(), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createEmptyObjectProvider(), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM,
Collections.emptyList()); Collections.emptyList());
postProcessAndInitialize(processor, composite); postProcessAndInitialize(processor, composite);
@@ -124,7 +126,8 @@ class MeterRegistryPostProcessorTests {
this.customizers.add(this.mockCustomizer); this.customizers.add(this.mockCustomizer);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
then(this.mockCustomizer).should().customize(this.mockRegistry); then(this.mockCustomizer).should().customize(this.mockRegistry);
} }
@@ -135,7 +138,8 @@ class MeterRegistryPostProcessorTests {
this.filters.add(this.mockFilter); this.filters.add(this.mockFilter);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
then(this.mockConfig).should().meterFilter(this.mockFilter); then(this.mockConfig).should().meterFilter(this.mockFilter);
} }
@@ -147,7 +151,8 @@ class MeterRegistryPostProcessorTests {
this.filters.add(onlyOnceFilter); this.filters.add(onlyOnceFilter);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM,
Collections.emptyList()); Collections.emptyList());
postProcessAndInitialize(processor, composite); postProcessAndInitialize(processor, composite);
@@ -162,7 +167,8 @@ class MeterRegistryPostProcessorTests {
this.binders.add(this.mockBinder); this.binders.add(this.mockBinder);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
then(this.mockBinder).should().bindTo(this.mockRegistry); then(this.mockBinder).should().bindTo(this.mockRegistry);
} }
@@ -184,8 +190,8 @@ class MeterRegistryPostProcessorTests {
given(this.mockRegistry.config()).willReturn(this.mockConfig); given(this.mockRegistry.config()).willReturn(this.mockConfig);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor( MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(
CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties), CompositeMeterRegistries.ONLY_USER_DEFINED, createObjectProvider(this.properties),
createObjectProvider(this.customizers), createObjectProvider(this.filters), createObjectProvider(this.customizers), createObjectProvider(this.filters), createEmptyObjectProvider(),
createEmptyObjectProvider(), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
then(this.mockBinder).shouldHaveNoInteractions(); then(this.mockBinder).shouldHaveNoInteractions();
} }
@@ -195,7 +201,8 @@ class MeterRegistryPostProcessorTests {
this.binders.add(this.mockBinder); this.binders.add(this.mockBinder);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createEmptyObjectProvider(), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createEmptyObjectProvider(), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM, AutoConfiguredCompositeMeterRegistry composite = new AutoConfiguredCompositeMeterRegistry(Clock.SYSTEM,
Collections.emptyList()); Collections.emptyList());
postProcessAndInitialize(processor, composite); postProcessAndInitialize(processor, composite);
@@ -207,7 +214,8 @@ class MeterRegistryPostProcessorTests {
this.binders.add(this.mockBinder); this.binders.add(this.mockBinder);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createEmptyObjectProvider(), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createEmptyObjectProvider(),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
CompositeMeterRegistry composite = new CompositeMeterRegistry(); CompositeMeterRegistry composite = new CompositeMeterRegistry();
postProcessAndInitialize(processor, composite); postProcessAndInitialize(processor, composite);
then(this.mockBinder).shouldHaveNoInteractions(); then(this.mockBinder).shouldHaveNoInteractions();
@@ -218,7 +226,8 @@ class MeterRegistryPostProcessorTests {
given(this.mockRegistry.config()).willReturn(this.mockConfig); given(this.mockRegistry.config()).willReturn(this.mockConfig);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.AUTO_CONFIGURED,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createEmptyObjectProvider(), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createEmptyObjectProvider(),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
then(this.mockBinder).shouldHaveNoInteractions(); then(this.mockBinder).shouldHaveNoInteractions();
} }
@@ -231,7 +240,8 @@ class MeterRegistryPostProcessorTests {
this.binders.add(this.mockBinder); this.binders.add(this.mockBinder);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
InOrder ordered = inOrder(this.mockBinder, this.mockConfig, this.mockCustomizer); InOrder ordered = inOrder(this.mockBinder, this.mockConfig, this.mockCustomizer);
then(this.mockCustomizer).should(ordered).customize(this.mockRegistry); then(this.mockCustomizer).should(ordered).customize(this.mockRegistry);
@@ -240,15 +250,16 @@ class MeterRegistryPostProcessorTests {
} }
@Test @Test
void trackedRegistryIsRemovedFromGlobalRegistryOnContextClosedEvent() { void postProcessAndInitializeWhenUseGlobalRegistryTrueAddsToGlobalRegistry() {
ApplicationContext applicationContext = mock(ApplicationContext.class); given(this.mockRegistry.config()).willReturn(this.mockConfig);
MetricsAutoConfiguration.MeterRegistryCloser closer = new MetricsAutoConfiguration.MeterRegistryCloser( this.properties.setUseGlobalRegistry(true);
applicationContext, true); MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
try { try {
Metrics.addRegistry(this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
closer.track(this.mockRegistry); assertThat(Metrics.globalRegistry.getRegistries()).contains(this.mockRegistry);
closer.onApplicationEvent(new org.springframework.context.event.ContextClosedEvent(applicationContext));
assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(this.mockRegistry);
} }
finally { finally {
Metrics.removeRegistry(this.mockRegistry); Metrics.removeRegistry(this.mockRegistry);
@@ -256,15 +267,17 @@ class MeterRegistryPostProcessorTests {
} }
@Test @Test
void postProcessAndInitializeWhenUseGlobalRegistryTrueAddsToGlobalRegistry() { void addToGlobalRegistryIfNecessaryTracksRegistryWithMeterRegistryCloser() {
given(this.mockRegistry.config()).willReturn(this.mockConfig); given(this.mockRegistry.config()).willReturn(this.mockConfig);
this.properties.setUseGlobalRegistry(true); this.properties.setUseGlobalRegistry(true);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
try { try {
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
assertThat(Metrics.globalRegistry.getRegistries()).contains(this.mockRegistry); this.meterRegistryCloser.onApplicationEvent(new ContextClosedEvent(this.meterRegistryCloserContext));
assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(this.mockRegistry);
} }
finally { finally {
Metrics.removeRegistry(this.mockRegistry); Metrics.removeRegistry(this.mockRegistry);
@@ -276,18 +289,32 @@ class MeterRegistryPostProcessorTests {
given(this.mockRegistry.config()).willReturn(this.mockConfig); given(this.mockRegistry.config()).willReturn(this.mockConfig);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry); postProcessAndInitialize(processor, this.mockRegistry);
assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(this.mockRegistry); assertThat(Metrics.globalRegistry.getRegistries()).doesNotContain(this.mockRegistry);
} }
@Test
void postProcessMeterRegistryTracksRegistryForClosingWithMeterRegistryCloser() {
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),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
postProcessAndInitialize(processor, this.mockRegistry);
this.meterRegistryCloser.onApplicationEvent(new ContextClosedEvent(this.meterRegistryCloserContext));
then(this.mockRegistry).should().close();
}
@Test @Test
void postProcessDoesNotBindToUntilSingletonsInitialized() { void postProcessDoesNotBindToUntilSingletonsInitialized() {
given(this.mockRegistry.config()).willReturn(this.mockConfig); given(this.mockRegistry.config()).willReturn(this.mockConfig);
this.binders.add(this.mockBinder); this.binders.add(this.mockBinder);
MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE, MeterRegistryPostProcessor processor = new MeterRegistryPostProcessor(CompositeMeterRegistries.NONE,
createObjectProvider(this.properties), createObjectProvider(this.customizers), createObjectProvider(this.properties), createObjectProvider(this.customizers),
createObjectProvider(this.filters), createObjectProvider(this.binders), createMeterRegistryCloserProvider(this.meterRegistryCloser)); createObjectProvider(this.filters), createObjectProvider(this.binders),
createMeterRegistryCloserProvider(this.meterRegistryCloser));
processor.postProcessAfterInitialization(this.mockRegistry, "meterRegistry"); processor.postProcessAfterInitialization(this.mockRegistry, "meterRegistry");
then(this.mockBinder).shouldHaveNoInteractions(); then(this.mockBinder).shouldHaveNoInteractions();
processor.afterSingletonsInstantiated(); processor.afterSingletonsInstantiated();
@@ -30,7 +30,6 @@ import io.micrometer.observation.ObservationHandler;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.micrometer.metrics.autoconfigure.MetricsAutoConfiguration.MeterRegistryCloser;
import org.springframework.boot.micrometer.observation.autoconfigure.ObservationHandlerGroup; import org.springframework.boot.micrometer.observation.autoconfigure.ObservationHandlerGroup;
import org.springframework.boot.test.context.runner.ApplicationContextRunner; import org.springframework.boot.test.context.runner.ApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;