Only enable full path optimization when there's one DispatcherServlet
Previously, UrlPathHelper's full path optimization was enabled when there was a dispatcher servlet mapped to /. The UrlPathHelper is used across Spring MVC and if there are multiple dispatcher servlets they all share the sample UrlPathHelper. This meant that any additional dispatcher servlets mapping to locations other than / would not be able to map requests correctly as the UrlPathHelper would use the full path, ignoring the url mapping of the dispatcher servlet. This commit updates the MVC auto-configuration so that use of the full path is only enabled if there's a single dispatcher servlet registration. Fixes gh-22682
This commit is contained in:
		
							parent
							
								
									980ddcffd0
								
							
						
					
					
						commit
						1270af90c2
					
				| 
						 | 
					@ -56,6 +56,7 @@ import org.springframework.boot.autoconfigure.web.format.WebConversionService;
 | 
				
			||||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
 | 
					import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
 | 
				
			||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
					import org.springframework.boot.context.properties.EnableConfigurationProperties;
 | 
				
			||||||
import org.springframework.boot.convert.ApplicationConversionService;
 | 
					import org.springframework.boot.convert.ApplicationConversionService;
 | 
				
			||||||
 | 
					import org.springframework.boot.web.servlet.ServletRegistrationBean;
 | 
				
			||||||
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
 | 
					import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
 | 
				
			||||||
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
 | 
					import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
 | 
				
			||||||
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
 | 
					import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
 | 
				
			||||||
| 
						 | 
					@ -191,18 +192,22 @@ public class WebMvcAutoConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
 | 
							private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
 | 
							final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
 | 
							public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
 | 
				
			||||||
				ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
 | 
									ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
 | 
				
			||||||
				ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
 | 
									ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
 | 
				
			||||||
				ObjectProvider<DispatcherServletPath> dispatcherServletPath) {
 | 
									ObjectProvider<DispatcherServletPath> dispatcherServletPath,
 | 
				
			||||||
 | 
									ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
 | 
				
			||||||
			this.resourceProperties = resourceProperties;
 | 
								this.resourceProperties = resourceProperties;
 | 
				
			||||||
			this.mvcProperties = mvcProperties;
 | 
								this.mvcProperties = mvcProperties;
 | 
				
			||||||
			this.beanFactory = beanFactory;
 | 
								this.beanFactory = beanFactory;
 | 
				
			||||||
			this.messageConvertersProvider = messageConvertersProvider;
 | 
								this.messageConvertersProvider = messageConvertersProvider;
 | 
				
			||||||
			this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
 | 
								this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
 | 
				
			||||||
			this.dispatcherServletPath = dispatcherServletPath;
 | 
								this.dispatcherServletPath = dispatcherServletPath;
 | 
				
			||||||
 | 
								this.servletRegistrations = servletRegistrations;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Override
 | 
							@Override
 | 
				
			||||||
| 
						 | 
					@ -234,7 +239,7 @@ public class WebMvcAutoConfiguration {
 | 
				
			||||||
					this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
 | 
										this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
 | 
				
			||||||
			this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
 | 
								this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
 | 
				
			||||||
				String servletUrlMapping = dispatcherPath.getServletUrlMapping();
 | 
									String servletUrlMapping = dispatcherPath.getServletUrlMapping();
 | 
				
			||||||
				if (servletUrlMapping.equals("/")) {
 | 
									if (servletUrlMapping.equals("/") && singleDispatcherServlet()) {
 | 
				
			||||||
					UrlPathHelper urlPathHelper = new UrlPathHelper();
 | 
										UrlPathHelper urlPathHelper = new UrlPathHelper();
 | 
				
			||||||
					urlPathHelper.setAlwaysUseFullPath(true);
 | 
										urlPathHelper.setAlwaysUseFullPath(true);
 | 
				
			||||||
					configurer.setUrlPathHelper(urlPathHelper);
 | 
										configurer.setUrlPathHelper(urlPathHelper);
 | 
				
			||||||
| 
						 | 
					@ -242,6 +247,11 @@ public class WebMvcAutoConfiguration {
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private boolean singleDispatcherServlet() {
 | 
				
			||||||
 | 
								return this.servletRegistrations.stream().map(ServletRegistrationBean::getServlet)
 | 
				
			||||||
 | 
										.filter(DispatcherServlet.class::isInstance).count() == 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		@Override
 | 
							@Override
 | 
				
			||||||
		@SuppressWarnings("deprecation")
 | 
							@SuppressWarnings("deprecation")
 | 
				
			||||||
		public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
 | 
							public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,6 +52,7 @@ import org.springframework.boot.test.context.runner.ContextConsumer;
 | 
				
			||||||
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
 | 
					import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
 | 
				
			||||||
import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
 | 
					import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor;
 | 
				
			||||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
					import org.springframework.boot.web.servlet.FilterRegistrationBean;
 | 
				
			||||||
 | 
					import org.springframework.boot.web.servlet.ServletRegistrationBean;
 | 
				
			||||||
import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
 | 
					import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter;
 | 
				
			||||||
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
 | 
					import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
 | 
				
			||||||
import org.springframework.context.ApplicationContext;
 | 
					import org.springframework.context.ApplicationContext;
 | 
				
			||||||
| 
						 | 
					@ -84,6 +85,7 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 | 
				
			||||||
import org.springframework.web.filter.FormContentFilter;
 | 
					import org.springframework.web.filter.FormContentFilter;
 | 
				
			||||||
import org.springframework.web.filter.HiddenHttpMethodFilter;
 | 
					import org.springframework.web.filter.HiddenHttpMethodFilter;
 | 
				
			||||||
import org.springframework.web.filter.RequestContextFilter;
 | 
					import org.springframework.web.filter.RequestContextFilter;
 | 
				
			||||||
 | 
					import org.springframework.web.servlet.DispatcherServlet;
 | 
				
			||||||
import org.springframework.web.servlet.HandlerAdapter;
 | 
					import org.springframework.web.servlet.HandlerAdapter;
 | 
				
			||||||
import org.springframework.web.servlet.HandlerExceptionResolver;
 | 
					import org.springframework.web.servlet.HandlerExceptionResolver;
 | 
				
			||||||
import org.springframework.web.servlet.HandlerMapping;
 | 
					import org.springframework.web.servlet.HandlerMapping;
 | 
				
			||||||
| 
						 | 
					@ -866,6 +868,23 @@ class WebMvcAutoConfigurationTests {
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void urlPathHelperDoesNotUseFullPathWithAdditionalDispatcherServlet() {
 | 
				
			||||||
 | 
							this.contextRunner.withUserConfiguration(AdditionalDispatcherServletConfiguration.class).run((context) -> {
 | 
				
			||||||
 | 
								UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class);
 | 
				
			||||||
 | 
								assertThat(urlPathHelper).extracting("alwaysUseFullPath").isEqualTo(false);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void urlPathHelperDoesNotUseFullPathWithAdditionalUntypedDispatcherServlet() {
 | 
				
			||||||
 | 
							this.contextRunner.withUserConfiguration(AdditionalUntypedDispatcherServletConfiguration.class)
 | 
				
			||||||
 | 
									.run((context) -> {
 | 
				
			||||||
 | 
										UrlPathHelper urlPathHelper = context.getBean(UrlPathHelper.class);
 | 
				
			||||||
 | 
										assertThat(urlPathHelper).extracting("alwaysUseFullPath").isEqualTo(false);
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void assertCacheControl(AssertableWebApplicationContext context) {
 | 
						private void assertCacheControl(AssertableWebApplicationContext context) {
 | 
				
			||||||
		Map<String, Object> handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));
 | 
							Map<String, Object> handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));
 | 
				
			||||||
		assertThat(handlerMap).hasSize(2);
 | 
							assertThat(handlerMap).hasSize(2);
 | 
				
			||||||
| 
						 | 
					@ -1248,4 +1267,24 @@ class WebMvcAutoConfigurationTests {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Configuration(proxyBeanMethods = false)
 | 
				
			||||||
 | 
						static class AdditionalDispatcherServletConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							ServletRegistrationBean<DispatcherServlet> additionalDispatcherServlet() {
 | 
				
			||||||
 | 
								return new ServletRegistrationBean<>(new DispatcherServlet());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Configuration(proxyBeanMethods = false)
 | 
				
			||||||
 | 
						static class AdditionalUntypedDispatcherServletConfiguration {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							@Bean
 | 
				
			||||||
 | 
							ServletRegistrationBean<?> additionalDispatcherServlet() {
 | 
				
			||||||
 | 
								return new ServletRegistrationBean<>(new DispatcherServlet());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue