From 3423c5dddb4150ec61b4c4dec52288056fc1a1db Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 10 Jul 2018 17:06:36 +0200 Subject: [PATCH] Detect DispatcherServlets registered via ServletRegistrationBean Previously, when a DispatcherServlet was registered via a ServletRegistrationBean, the mappings endpoint did not expose any information about it as it wasn't detected. This commit fixes the detection of available DispatcherServlets in the context so that the mappings endpoint include them all. Closes gh-13186 --- ...herServletsMappingDescriptionProvider.java | 33 +++++- .../web/mappings/MappingsEndpointTests.java | 100 +++++++++++++++--- .../web/servlet/ServletRegistrationBean.java | 17 +-- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java index 18f9613b200..c70f1de76c4 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/web/mappings/servlet/DispatcherServletsMappingDescriptionProvider.java @@ -19,14 +19,18 @@ package org.springframework.boot.actuate.web.mappings.servlet; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.servlet.Servlet; + import org.springframework.boot.actuate.web.mappings.HandlerMethodDescription; import org.springframework.boot.actuate.web.mappings.MappingDescriptionProvider; +import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.data.rest.webmvc.support.DelegatingHandlerMapping; import org.springframework.util.ClassUtils; @@ -44,6 +48,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi * DispatcherServlets}. * * @author Andy Wilkinson + * @author Stephane Nicoll * @since 2.0.0 */ public class DispatcherServletsMappingDescriptionProvider @@ -81,13 +86,33 @@ public class DispatcherServletsMappingDescriptionProvider private Map> describeMappings( WebApplicationContext context) { Map> mappings = new HashMap<>(); - context.getBeansOfType(DispatcherServlet.class) - .forEach((name, dispatcherServlet) -> mappings.put(name, - describeMappings(new DispatcherServletHandlerMappings(name, - dispatcherServlet, context)))); + determineDispatcherServlets(context).forEach((name, dispatcherServlet) -> mappings + .put(name, describeMappings(new DispatcherServletHandlerMappings(name, + dispatcherServlet, context)))); return mappings; } + private Map determineDispatcherServlets( + WebApplicationContext context) { + Map dispatcherServlets = new LinkedHashMap<>(); + context.getBeansOfType(ServletRegistrationBean.class).values() + .forEach((registration) -> { + Servlet servlet = registration.getServlet(); + if (servlet instanceof DispatcherServlet + && !dispatcherServlets.containsValue(servlet)) { + dispatcherServlets.put(registration.getServletName(), + (DispatcherServlet) servlet); + } + }); + context.getBeansOfType(DispatcherServlet.class) + .forEach((name, dispatcherServlet) -> { + if (!dispatcherServlets.containsValue(dispatcherServlet)) { + dispatcherServlets.put(name, dispatcherServlet); + } + }); + return dispatcherServlets; + } + private List describeMappings( DispatcherServletHandlerMappings mappings) { return mappings.getHandlerMappings().stream().flatMap(this::describe) diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java index 7a92d9ebcd3..981c431be1f 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/web/mappings/MappingsEndpointTests.java @@ -41,6 +41,7 @@ import org.springframework.boot.actuate.web.mappings.servlet.ServletRegistration import org.springframework.boot.actuate.web.mappings.servlet.ServletsMappingDescriptionProvider; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -67,28 +68,14 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r * Tests for {@link MappingsEndpoint}. * * @author Andy Wilkinson + * @author Stephane Nicoll */ public class MappingsEndpointTests { @Test @SuppressWarnings("unchecked") public void servletWebMappings() { - ServletContext servletContext = mock(ServletContext.class); - given(servletContext.getInitParameterNames()) - .willReturn(Collections.emptyEnumeration()); - given(servletContext.getAttributeNames()) - .willReturn(Collections.emptyEnumeration()); - FilterRegistration filterRegistration = mock(FilterRegistration.class); - given((Map) servletContext.getFilterRegistrations()) - .willReturn(Collections.singletonMap("testFilter", filterRegistration)); - ServletRegistration servletRegistration = mock(ServletRegistration.class); - given((Map) servletContext.getServletRegistrations()) - .willReturn(Collections.singletonMap("testServlet", servletRegistration)); - Supplier contextSupplier = () -> { - AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); - context.setServletContext(servletContext); - return context; - }; + Supplier contextSupplier = prepareContextSupplier(); new WebApplicationContextRunner(contextSupplier) .withUserConfiguration(EndpointConfiguration.class, ServletWebConfiguration.class) @@ -112,6 +99,47 @@ public class MappingsEndpointTests { }); } + @Test + @SuppressWarnings("unchecked") + public void servletWebMappingsWithAdditionalDispatcherServlets() { + Supplier contextSupplier = prepareContextSupplier(); + new WebApplicationContextRunner(contextSupplier).withUserConfiguration( + EndpointConfiguration.class, ServletWebConfiguration.class, + CustomDispatcherServletConfiguration.class).run((context) -> { + ContextMappings contextMappings = contextMappings(context); + Map> dispatcherServlets = mappings( + contextMappings, "dispatcherServlets"); + assertThat(dispatcherServlets).containsOnlyKeys("dispatcherServlet", + "customDispatcherServletRegistration", + "anotherDispatcherServletRegistration"); + assertThat(dispatcherServlets.get("dispatcherServlet")).hasSize(1); + assertThat( + dispatcherServlets.get("customDispatcherServletRegistration")) + .hasSize(1); + assertThat(dispatcherServlets + .get("anotherDispatcherServletRegistration")).hasSize(1); + }); + } + + private Supplier prepareContextSupplier() { + ServletContext servletContext = mock(ServletContext.class); + given(servletContext.getInitParameterNames()) + .willReturn(Collections.emptyEnumeration()); + given(servletContext.getAttributeNames()) + .willReturn(Collections.emptyEnumeration()); + FilterRegistration filterRegistration = mock(FilterRegistration.class); + given((Map) servletContext.getFilterRegistrations()) + .willReturn(Collections.singletonMap("testFilter", filterRegistration)); + ServletRegistration servletRegistration = mock(ServletRegistration.class); + given((Map) servletContext.getServletRegistrations()) + .willReturn(Collections.singletonMap("testServlet", servletRegistration)); + return () -> { + AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); + context.setServletContext(servletContext); + return context; + }; + } + @Test public void reactiveWebMappings() { new ReactiveWebApplicationContextRunner() @@ -213,4 +241,44 @@ public class MappingsEndpointTests { } + @Configuration + static class CustomDispatcherServletConfiguration { + + @Bean + public ServletRegistrationBean customDispatcherServletRegistration( + WebApplicationContext context) { + ServletRegistrationBean registration = new ServletRegistrationBean<>( + createTestDispatcherServlet(context)); + registration.setName("customDispatcherServletRegistration"); + return registration; + } + + @Bean + public DispatcherServlet anotherDispatcherServlet(WebApplicationContext context) { + return createTestDispatcherServlet(context); + } + + @Bean + public ServletRegistrationBean anotherDispatcherServletRegistration( + WebApplicationContext context) { + ServletRegistrationBean registrationBean = new ServletRegistrationBean<>( + anotherDispatcherServlet(context)); + registrationBean.setName("anotherDispatcherServletRegistration"); + return registrationBean; + } + + private DispatcherServlet createTestDispatcherServlet( + WebApplicationContext context) { + try { + DispatcherServlet dispatcherServlet = new DispatcherServlet(context); + dispatcherServlet.init(new MockServletConfig()); + return dispatcherServlet; + } + catch (ServletException ex) { + throw new IllegalStateException(ex); + } + } + + } + } diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistrationBean.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistrationBean.java index 70324069145..cbdee46d2a4 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistrationBean.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/servlet/ServletRegistrationBean.java @@ -100,14 +100,6 @@ public class ServletRegistrationBean this.urlMappings.addAll(Arrays.asList(urlMappings)); } - /** - * Returns the servlet being registered. - * @return the servlet - */ - protected T getServlet() { - return this.servlet; - } - /** * Sets the servlet to be registered. * @param servlet the servlet @@ -117,6 +109,15 @@ public class ServletRegistrationBean this.servlet = servlet; } + /** + * Return the servlet being registered. + * @return the servlet + * @since 2.0.4 + */ + public T getServlet() { + return this.servlet; + } + /** * Set the URL mappings for the servlet. If not specified the mapping will default to * '/'. This will replace any previously specified mappings.