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.