From e72506c35368c412ad020ab4d15f8f32798c50d1 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Fri, 16 Feb 2018 09:07:12 +0000 Subject: [PATCH] Protect TomcatMetrics auto-config against early MeterBinder init Closes gh-11916 --- .../TomcatMetricsAutoConfiguration.java | 41 ++++++++--------- .../TomcatMetricsAutoConfigurationTests.java | 46 +++++++++++++++++-- 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java index 7403157ba3..a87be682f9 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfiguration.java @@ -19,7 +19,6 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat; import java.util.Collections; import io.micrometer.core.instrument.binder.tomcat.TomcatMetrics; -import org.apache.catalina.Container; import org.apache.catalina.Context; import org.apache.catalina.Manager; @@ -27,9 +26,10 @@ import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; -import org.springframework.boot.web.context.WebServerApplicationContext; -import org.springframework.boot.web.embedded.tomcat.TomcatWebServer; -import org.springframework.boot.web.server.WebServer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type; +import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.server.WebServerFactoryCustomizer; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; @@ -43,32 +43,29 @@ import org.springframework.context.annotation.Bean; @ConditionalOnClass({ TomcatMetrics.class, Manager.class }) public class TomcatMetricsAutoConfiguration { + private volatile Context context; + @Bean @ConditionalOnMissingBean(TomcatMetrics.class) public TomcatMetrics tomcatMetrics(ApplicationContext applicationContext) { - Context context = findContext(applicationContext); - return new TomcatMetrics(context == null ? null : context.getManager(), + return new TomcatMetrics(this.context == null ? null : this.context.getManager(), Collections.emptyList()); } - private Context findContext(ApplicationContext context) { - if (!(context instanceof WebServerApplicationContext)) { - return null; - } - WebServer webServer = ((WebServerApplicationContext) context).getWebServer(); - if (!(webServer instanceof TomcatWebServer)) { - return null; - } - return findContext((TomcatWebServer) webServer); + @Bean + @ConditionalOnWebApplication(type = Type.SERVLET) + public WebServerFactoryCustomizer contextCapturingServletTomcatCustomizer() { + return (tomcatFactory) -> tomcatFactory.addContextCustomizers(this::setContext); } - private Context findContext(TomcatWebServer webServer) { - for (Container child : webServer.getTomcat().getHost().findChildren()) { - if (child instanceof Context) { - return (Context) child; - } - } - return null; + @Bean + @ConditionalOnWebApplication(type = Type.REACTIVE) + public WebServerFactoryCustomizer contextCapturingReactiveTomcatCustomizer() { + return (tomcatFactory) -> tomcatFactory.addContextCustomizers(this::setContext); + } + + private void setContext(Context context) { + this.context = context; } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java index dad59466a3..5d2f61e874 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/web/tomcat/TomcatMetricsAutoConfigurationTests.java @@ -18,17 +18,22 @@ package org.springframework.boot.actuate.autoconfigure.metrics.web.tomcat; import java.util.Collections; +import io.micrometer.core.instrument.binder.MeterBinder; import io.micrometer.core.instrument.binder.tomcat.TomcatMetrics; import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import org.junit.Test; import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerAutoConfiguration; +import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; import org.springframework.boot.web.embedded.tomcat.TomcatReactiveWebServerFactory; import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext; +import org.springframework.boot.web.servlet.ServletContextInitializer; import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; +import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.server.reactive.HttpHandler; @@ -47,8 +52,9 @@ public class TomcatMetricsAutoConfigurationTests { public void autoConfiguresTomcatMetricsWithEmbeddedServletTomcat() { new WebApplicationContextRunner( AnnotationConfigServletWebServerApplicationContext::new) - .withConfiguration(AutoConfigurations - .of(TomcatMetricsAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of( + TomcatMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) .withUserConfiguration(ServletWebServerConfiguration.class) .run((context) -> { assertThat(context).hasSingleBean(TomcatMetrics.class); @@ -62,12 +68,34 @@ public class TomcatMetricsAutoConfigurationTests { }); } + @Test + public void sessionMetricsAreAvailableWhenEarlyMeterBinderInitializationOccurs() { + new WebApplicationContextRunner( + AnnotationConfigServletWebServerApplicationContext::new) + .withConfiguration(AutoConfigurations.of( + TomcatMetricsAutoConfiguration.class, + ServletWebServerFactoryAutoConfiguration.class)) + .withUserConfiguration(ServletWebServerConfiguration.class, + EarlyMeterBinderInitializationConfiguration.class) + .run((context) -> { + assertThat(context).hasSingleBean(TomcatMetrics.class); + SimpleMeterRegistry registry = new SimpleMeterRegistry(); + context.getBean(TomcatMetrics.class).bindTo(registry); + assertThat( + registry.find("tomcat.sessions.active.max").meter()) + .isNotNull(); + assertThat(registry.find("tomcat.threads.current").meter()) + .isNotNull(); + }); + } + @Test public void autoConfiguresTomcatMetricsWithEmbeddedReactiveTomcat() { new ReactiveWebApplicationContextRunner( AnnotationConfigReactiveWebServerApplicationContext::new) - .withConfiguration(AutoConfigurations - .of(TomcatMetricsAutoConfiguration.class)) + .withConfiguration(AutoConfigurations.of( + TomcatMetricsAutoConfiguration.class, + ReactiveWebServerAutoConfiguration.class)) .withUserConfiguration(ReactiveWebServerConfiguration.class) .run((context) -> { assertThat(context).hasSingleBean(TomcatMetrics.class); @@ -134,4 +162,14 @@ public class TomcatMetricsAutoConfigurationTests { } + @Configuration + static class EarlyMeterBinderInitializationConfiguration { + + @Bean + public ServletContextInitializer earlyInitializer(ApplicationContext context) { + return (servletContext) -> context.getBeansOfType(MeterBinder.class); + } + + } + }