Skip to content

Commit 9a9111a

Browse files
committed
Support path discovery for main dispatcher servlet
Add an `DispatcherServletPath` interface which provides a much more consistent way to discover the path of the main dispatcher servet. Prior to this commit, auto-configurations would often make use of the `ServerProperties` class to discover the dispatcher servlet path. This mechanism isn't very explicit and also makes it hard for us to relocate that property in Spring Boot 2.1. This commit also reverts most of fddc9e9 since it is now clear that the supporting multiple dispatcher servlet paths will be much more involved that we originally anticipated. Closes gh-13834
1 parent d37e717 commit 9a9111a

File tree

22 files changed

+501
-260
lines changed

22 files changed

+501
-260
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfiguration.java

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
1818

19-
import java.util.Set;
20-
import java.util.stream.Collectors;
21-
2219
import org.glassfish.jersey.server.ResourceConfig;
2320

2421
import org.springframework.boot.actuate.autoconfigure.endpoint.ExposeExcludePropertyEndpointFilter;
@@ -30,11 +27,10 @@
3027
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
3128
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
3229
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
33-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
30+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
3431
import org.springframework.context.ApplicationContext;
3532
import org.springframework.context.annotation.Bean;
3633
import org.springframework.context.annotation.Configuration;
37-
import org.springframework.util.StringUtils;
3834
import org.springframework.web.servlet.DispatcherServlet;
3935

4036
/**
@@ -72,27 +68,13 @@ public WebMvcServletEndpointManagementContextConfiguration(
7268
public ServletEndpointRegistrar servletEndpointRegistrar(
7369
WebEndpointProperties properties,
7470
ServletEndpointsSupplier servletEndpointsSupplier) {
75-
DispatcherServletPathProvider servletPathProvider = this.context
76-
.getBean(DispatcherServletPathProvider.class);
77-
Set<String> cleanedPaths = getServletPaths(properties, servletPathProvider);
78-
return new ServletEndpointRegistrar(cleanedPaths,
71+
DispatcherServletPath dispatcherServletPath = this.context
72+
.getBean(DispatcherServletPath.class);
73+
return new ServletEndpointRegistrar(
74+
dispatcherServletPath.getRelativePath(properties.getBasePath()),
7975
servletEndpointsSupplier.getEndpoints());
8076
}
8177

82-
private Set<String> getServletPaths(WebEndpointProperties properties,
83-
DispatcherServletPathProvider servletPathProvider) {
84-
return servletPathProvider.getServletPaths().stream()
85-
.map((p) -> cleanServletPath(p) + properties.getBasePath())
86-
.collect(Collectors.toSet());
87-
}
88-
89-
private String cleanServletPath(String servletPath) {
90-
if (StringUtils.hasText(servletPath) && servletPath.endsWith("/")) {
91-
return servletPath.substring(0, servletPath.length() - 1);
92-
}
93-
return servletPath;
94-
}
95-
9678
}
9779

9880
@Configuration

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequest.java

Lines changed: 18 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
import org.springframework.boot.actuate.autoconfigure.endpoint.web.WebEndpointProperties;
3434
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
3535
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
36-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
36+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
3737
import org.springframework.boot.security.servlet.ApplicationContextRequestMatcher;
3838
import org.springframework.core.annotation.AnnotatedElementUtils;
3939
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@@ -137,23 +137,20 @@ protected final boolean matches(HttpServletRequest request,
137137

138138
private RequestMatcher createDelegate(WebApplicationContext context) {
139139
try {
140-
Set<String> servletPaths = getServletPaths(context);
141-
RequestMatcherFactory requestMatcherFactory = new RequestMatcherFactory(
142-
servletPaths);
143-
return createDelegate(context, requestMatcherFactory);
140+
String pathPrefix = getPathPrefix(context);
141+
return createDelegate(context, new RequestMatcherFactory(pathPrefix));
144142
}
145143
catch (NoSuchBeanDefinitionException ex) {
146144
return EMPTY_MATCHER;
147145
}
148146
}
149147

150-
private Set<String> getServletPaths(WebApplicationContext context) {
148+
private String getPathPrefix(WebApplicationContext context) {
151149
try {
152-
return context.getBean(DispatcherServletPathProvider.class)
153-
.getServletPaths();
150+
return context.getBean(DispatcherServletPath.class).getPrefix();
154151
}
155152
catch (NoSuchBeanDefinitionException ex) {
156-
return Collections.singleton("");
153+
return "";
157154
}
158155
}
159156

@@ -225,7 +222,7 @@ protected RequestMatcher createDelegate(WebApplicationContext context,
225222
requestMatcherFactory, paths);
226223
if (this.includeLinks
227224
&& StringUtils.hasText(pathMappedEndpoints.getBasePath())) {
228-
delegateMatchers.addAll(
225+
delegateMatchers.add(
229226
requestMatcherFactory.antPath(pathMappedEndpoints.getBasePath()));
230227
}
231228
return new OrRequestMatcher(delegateMatchers);
@@ -258,8 +255,7 @@ private String getEndpointId(Class<?> source) {
258255
private List<RequestMatcher> getDelegateMatchers(
259256
RequestMatcherFactory requestMatcherFactory, Set<String> paths) {
260257
return paths.stream()
261-
.flatMap(
262-
(path) -> requestMatcherFactory.antPath(path, "/**").stream())
258+
.map((path) -> requestMatcherFactory.antPath(path, "/**"))
263259
.collect(Collectors.toList());
264260
}
265261

@@ -276,9 +272,7 @@ protected RequestMatcher createDelegate(WebApplicationContext context,
276272
WebEndpointProperties properties = context
277273
.getBean(WebEndpointProperties.class);
278274
if (StringUtils.hasText(properties.getBasePath())) {
279-
List<RequestMatcher> matchers = requestMatcherFactory
280-
.antPath(properties.getBasePath());
281-
return new OrRequestMatcher(matchers);
275+
return requestMatcherFactory.antPath(properties.getBasePath());
282276
}
283277
return EMPTY_MATCHER;
284278
}
@@ -290,19 +284,18 @@ protected RequestMatcher createDelegate(WebApplicationContext context,
290284
*/
291285
private static class RequestMatcherFactory {
292286

293-
private final Set<String> servletPaths = new LinkedHashSet<>();
287+
private final String prefix;
294288

295-
RequestMatcherFactory(Set<String> servletPaths) {
296-
this.servletPaths.addAll(servletPaths);
289+
RequestMatcherFactory(String prefix) {
290+
this.prefix = prefix;
297291
}
298292

299-
List<RequestMatcher> antPath(String... parts) {
300-
return this.servletPaths.stream()
301-
.map((p) -> (StringUtils.hasText(p) && !p.equals("/") ? p : ""))
302-
.distinct()
303-
.map((path) -> Arrays.stream(parts)
304-
.collect(Collectors.joining("", path, "")))
305-
.map(AntPathRequestMatcher::new).collect(Collectors.toList());
293+
public RequestMatcher antPath(String... parts) {
294+
String pattern = this.prefix;
295+
for (String part : parts) {
296+
pattern += part;
297+
}
298+
return new AntPathRequestMatcher(pattern);
306299
}
307300

308301
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfiguration.java

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616

1717
package org.springframework.boot.actuate.autoconfigure.web.servlet;
1818

19-
import java.util.Collections;
20-
2119
import org.springframework.beans.factory.ListableBeanFactory;
2220
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextConfiguration;
2321
import org.springframework.boot.actuate.autoconfigure.web.ManagementContextType;
@@ -27,7 +25,7 @@
2725
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
2826
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
2927
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
30-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
28+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletRegistrationBean;
3129
import org.springframework.boot.web.servlet.error.ErrorAttributes;
3230
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
3331
import org.springframework.context.annotation.Bean;
@@ -72,6 +70,12 @@ public DispatcherServlet dispatcherServlet() {
7270
return dispatcherServlet;
7371
}
7472

73+
@Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
74+
public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(
75+
DispatcherServlet dispatcherServlet) {
76+
return new DispatcherServletRegistrationBean(dispatcherServlet, "/");
77+
}
78+
7579
@Bean(name = DispatcherServlet.HANDLER_MAPPING_BEAN_NAME)
7680
public CompositeHandlerMapping compositeHandlerMapping() {
7781
return new CompositeHandlerMapping();
@@ -95,9 +99,4 @@ public RequestContextFilter requestContextFilter() {
9599
return new OrderedRequestContextFilter();
96100
}
97101

98-
@Bean
99-
public DispatcherServletPathProvider childDispatcherServletPathProvider() {
100-
return () -> Collections.singleton("");
101-
}
102-
103102
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/endpoint/web/ServletEndpointManagementContextConfigurationTests.java

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,13 @@
1717
package org.springframework.boot.actuate.autoconfigure.endpoint.web;
1818

1919
import java.util.Collections;
20-
import java.util.LinkedHashSet;
21-
import java.util.Set;
2220

2321
import org.glassfish.jersey.server.ResourceConfig;
2422
import org.junit.Test;
2523

2624
import org.springframework.boot.actuate.endpoint.web.ServletEndpointRegistrar;
2725
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpointsSupplier;
28-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
26+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
2927
import org.springframework.boot.context.properties.EnableConfigurationProperties;
3028
import org.springframework.boot.test.context.FilteredClassLoader;
3129
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
@@ -50,32 +48,27 @@ public class ServletEndpointManagementContextConfigurationTests {
5048
.withUserConfiguration(TestConfig.class);
5149

5250
@Test
53-
@SuppressWarnings("unchecked")
5451
public void contextShouldContainServletEndpointRegistrar() {
5552
FilteredClassLoader classLoader = new FilteredClassLoader(ResourceConfig.class);
5653
this.contextRunner.withClassLoader(classLoader).run((context) -> {
5754
assertThat(context).hasSingleBean(ServletEndpointRegistrar.class);
5855
ServletEndpointRegistrar bean = context
5956
.getBean(ServletEndpointRegistrar.class);
60-
Set<String> basePaths = (Set<String>) ReflectionTestUtils.getField(bean,
61-
"basePaths");
62-
assertThat(basePaths).containsExactlyInAnyOrder("/test/actuator", "/actuator",
63-
"/foo/actuator");
57+
String basePath = (String) ReflectionTestUtils.getField(bean, "basePath");
58+
assertThat(basePath).isEqualTo("/test/actuator");
6459
});
6560
}
6661

6762
@Test
68-
@SuppressWarnings("unchecked")
6963
public void servletPathShouldNotAffectJerseyConfiguration() {
7064
FilteredClassLoader classLoader = new FilteredClassLoader(
7165
DispatcherServlet.class);
7266
this.contextRunner.withClassLoader(classLoader).run((context) -> {
7367
assertThat(context).hasSingleBean(ServletEndpointRegistrar.class);
7468
ServletEndpointRegistrar bean = context
7569
.getBean(ServletEndpointRegistrar.class);
76-
Set<String> basePaths = (Set<String>) ReflectionTestUtils.getField(bean,
77-
"basePaths");
78-
assertThat(basePaths).containsExactly("/actuator");
70+
String basePath = (String) ReflectionTestUtils.getField(bean, "basePath");
71+
assertThat(basePath).isEqualTo("/actuator");
7972
});
8073
}
8174

@@ -97,14 +90,8 @@ public ServletEndpointsSupplier servletEndpointsSupplier() {
9790
}
9891

9992
@Bean
100-
public DispatcherServletPathProvider servletPathProvider() {
101-
return () -> {
102-
Set<String> paths = new LinkedHashSet<>();
103-
paths.add("/");
104-
paths.add("/test");
105-
paths.add("/foo/");
106-
return paths;
107-
};
93+
public DispatcherServletPath dispatcherServletPath() {
94+
return () -> "/test";
10895
}
10996

11097
}

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/security/servlet/EndpointRequestTests.java

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717
package org.springframework.boot.actuate.autoconfigure.security.servlet;
1818

1919
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.LinkedHashSet;
2220
import java.util.List;
2321

2422
import javax.servlet.http.HttpServletRequest;
@@ -33,7 +31,7 @@
3331
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoint;
3432
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
3533
import org.springframework.boot.actuate.endpoint.web.annotation.ServletEndpoint;
36-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
34+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
3735
import org.springframework.mock.web.MockHttpServletRequest;
3836
import org.springframework.mock.web.MockServletContext;
3937
import org.springframework.security.web.util.matcher.RequestMatcher;
@@ -78,12 +76,11 @@ public void toAnyEndpointShouldNotMatchOtherPath() {
7876
@Test
7977
public void toAnyEndpointWhenServletPathNotEmptyShouldMatch() {
8078
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
81-
assertMatcher(matcher, "/actuator", "/spring", "/admin")
82-
.matches(Arrays.asList("/spring", "/admin"), "/actuator/foo");
83-
assertMatcher(matcher, "/actuator", "/spring", "/admin")
84-
.matches(Arrays.asList("/spring", "/admin"), "/actuator/bar");
85-
assertMatcher(matcher, "/actuator", "/spring").matches(Arrays.asList("/spring"),
86-
"/actuator");
79+
assertMatcher(matcher, "/actuator", "/spring").matches("/spring",
80+
"/actuator/foo");
81+
assertMatcher(matcher, "/actuator", "/spring").matches("/spring",
82+
"/actuator/bar");
83+
assertMatcher(matcher, "/actuator", "/spring").matches("/spring", "/actuator");
8784
assertMatcher(matcher, "/actuator", "/spring").doesNotMatch("/spring",
8885
"/actuator/baz");
8986
assertMatcher(matcher, "/actuator", "/spring").doesNotMatch("", "/actuator/foo");
@@ -92,10 +89,10 @@ public void toAnyEndpointWhenServletPathNotEmptyShouldMatch() {
9289
@Test
9390
public void toAnyEndpointWhenDispatcherServletPathProviderNotAvailableUsesEmptyPath() {
9491
RequestMatcher matcher = EndpointRequest.toAnyEndpoint();
95-
assertMatcher(matcher, "/actuator", (String) null).matches("/actuator/foo");
96-
assertMatcher(matcher, "/actuator", (String) null).matches("/actuator/bar");
97-
assertMatcher(matcher, "/actuator", (String) null).matches("/actuator");
98-
assertMatcher(matcher, "/actuator", (String) null).doesNotMatch("/actuator/baz");
92+
assertMatcher(matcher, "/actuator", null).matches("/actuator/foo");
93+
assertMatcher(matcher, "/actuator", null).matches("/actuator/bar");
94+
assertMatcher(matcher, "/actuator", null).matches("/actuator");
95+
assertMatcher(matcher, "/actuator", null).doesNotMatch("/actuator/baz");
9996
}
10097

10198
@Test
@@ -222,8 +219,8 @@ private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePa
222219
}
223220

224221
private RequestMatcherAssert assertMatcher(RequestMatcher matcher, String basePath,
225-
String... servletPaths) {
226-
return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPaths);
222+
String servletPath) {
223+
return assertMatcher(matcher, mockPathMappedEndpoints(basePath), servletPath);
227224
}
228225

229226
private PathMappedEndpoints mockPathMappedEndpoints(String basePath) {
@@ -246,7 +243,7 @@ private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
246243
}
247244

248245
private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
249-
PathMappedEndpoints pathMappedEndpoints, String... servletPaths) {
246+
PathMappedEndpoints pathMappedEndpoints, String dispatcherServletPath) {
250247
StaticWebApplicationContext context = new StaticWebApplicationContext();
251248
context.registerBean(WebEndpointProperties.class);
252249
if (pathMappedEndpoints != null) {
@@ -257,10 +254,9 @@ private RequestMatcherAssert assertMatcher(RequestMatcher matcher,
257254
properties.setBasePath(pathMappedEndpoints.getBasePath());
258255
}
259256
}
260-
if (servletPaths != null) {
261-
DispatcherServletPathProvider pathProvider = () -> new LinkedHashSet<>(
262-
Arrays.asList(servletPaths));
263-
context.registerBean(DispatcherServletPathProvider.class, () -> pathProvider);
257+
if (dispatcherServletPath != null) {
258+
DispatcherServletPath path = () -> dispatcherServletPath;
259+
context.registerBean(DispatcherServletPath.class, () -> path);
264260
}
265261
return assertThat(new RequestMatcherAssert(context, matcher));
266262
}
@@ -280,8 +276,8 @@ public void matches(String servletPath) {
280276
matches(mockRequest(servletPath));
281277
}
282278

283-
public void matches(List<String> servletPaths, String pathInfo) {
284-
servletPaths.forEach((p) -> matches(mockRequest(p, pathInfo)));
279+
public void matches(String servletPath, String pathInfo) {
280+
matches(mockRequest(servletPath, pathInfo));
285281
}
286282

287283
private void matches(HttpServletRequest request) {

spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/web/servlet/WebMvcEndpointChildContextConfigurationTests.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
import org.junit.Test;
2020

21-
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPathProvider;
21+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletPath;
2222
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
2323
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
2424
import org.springframework.context.annotation.Bean;
@@ -64,12 +64,12 @@ public void contextShouldNotConfigureRequestContextFilterWhenRequestContextListe
6464
}
6565

6666
@Test
67-
public void contextShouldConfigureDispatcherServletPathProviderWithEmptyPath() {
67+
public void contextShouldConfigureDispatcherServletPathWithRootPath() {
6868
this.contextRunner
6969
.withUserConfiguration(WebMvcEndpointChildContextConfiguration.class)
70-
.run((context) -> assertThat(context
71-
.getBean(DispatcherServletPathProvider.class).getServletPaths())
72-
.containsExactly(""));
70+
.run((context) -> assertThat(
71+
context.getBean(DispatcherServletPath.class).getPath())
72+
.isEqualTo("/"));
7373
}
7474

7575
static class ExistingConfig {

0 commit comments

Comments
 (0)