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
This commit is contained in:
Stephane Nicoll
2018-07-10 17:06:36 +02:00
parent dd2b0eb739
commit 3423c5dddb
3 changed files with 122 additions and 28 deletions

View File

@@ -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<String, List<DispatcherServletMappingDescription>> describeMappings(
WebApplicationContext context) {
Map<String, List<DispatcherServletMappingDescription>> 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<String, DispatcherServlet> determineDispatcherServlets(
WebApplicationContext context) {
Map<String, DispatcherServlet> 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<DispatcherServletMappingDescription> describeMappings(
DispatcherServletHandlerMappings mappings) {
return mappings.getHandlerMappings().stream().flatMap(this::describe)

View File

@@ -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<String, FilterRegistration>) servletContext.getFilterRegistrations())
.willReturn(Collections.singletonMap("testFilter", filterRegistration));
ServletRegistration servletRegistration = mock(ServletRegistration.class);
given((Map<String, ServletRegistration>) servletContext.getServletRegistrations())
.willReturn(Collections.singletonMap("testServlet", servletRegistration));
Supplier<ConfigurableWebApplicationContext> contextSupplier = () -> {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setServletContext(servletContext);
return context;
};
Supplier<ConfigurableWebApplicationContext> contextSupplier = prepareContextSupplier();
new WebApplicationContextRunner(contextSupplier)
.withUserConfiguration(EndpointConfiguration.class,
ServletWebConfiguration.class)
@@ -112,6 +99,47 @@ public class MappingsEndpointTests {
});
}
@Test
@SuppressWarnings("unchecked")
public void servletWebMappingsWithAdditionalDispatcherServlets() {
Supplier<ConfigurableWebApplicationContext> contextSupplier = prepareContextSupplier();
new WebApplicationContextRunner(contextSupplier).withUserConfiguration(
EndpointConfiguration.class, ServletWebConfiguration.class,
CustomDispatcherServletConfiguration.class).run((context) -> {
ContextMappings contextMappings = contextMappings(context);
Map<String, List<DispatcherServletMappingDescription>> 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<ConfigurableWebApplicationContext> 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<String, FilterRegistration>) servletContext.getFilterRegistrations())
.willReturn(Collections.singletonMap("testFilter", filterRegistration));
ServletRegistration servletRegistration = mock(ServletRegistration.class);
given((Map<String, ServletRegistration>) 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<DispatcherServlet> customDispatcherServletRegistration(
WebApplicationContext context) {
ServletRegistrationBean<DispatcherServlet> registration = new ServletRegistrationBean<>(
createTestDispatcherServlet(context));
registration.setName("customDispatcherServletRegistration");
return registration;
}
@Bean
public DispatcherServlet anotherDispatcherServlet(WebApplicationContext context) {
return createTestDispatcherServlet(context);
}
@Bean
public ServletRegistrationBean<DispatcherServlet> anotherDispatcherServletRegistration(
WebApplicationContext context) {
ServletRegistrationBean<DispatcherServlet> 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);
}
}
}
}

View File

@@ -100,14 +100,6 @@ public class ServletRegistrationBean<T extends Servlet>
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<T extends Servlet>
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.