diff --git a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 3d9ccdfa3e..2e8de24cac 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -84,6 +84,7 @@ import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.accept.ContentNegotiationManager; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; +import org.springframework.web.context.ServletContextAware; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextListener; @@ -182,7 +183,11 @@ public class WebMvcAutoConfiguration { @EnableConfigurationProperties({ WebMvcProperties.class, org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class }) @Order(0) - public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { + public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ServletContextAware { + + private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); + + private final Resources resourceProperties; private final WebMvcProperties mvcProperties; @@ -194,13 +199,19 @@ public class WebMvcAutoConfiguration { private final ObjectProvider> servletRegistrations; - final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; + private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; - public WebMvcAutoConfigurationAdapter(WebProperties webProperties, WebMvcProperties mvcProperties, - ListableBeanFactory beanFactory, ObjectProvider messageConvertersProvider, + private ServletContext servletContext; + + public WebMvcAutoConfigurationAdapter( + org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties, + WebProperties webProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, + ObjectProvider messageConvertersProvider, ObjectProvider resourceHandlerRegistrationCustomizerProvider, ObjectProvider dispatcherServletPath, ObjectProvider> servletRegistrations) { + this.resourceProperties = resourceProperties.hasBeenCustomized() ? resourceProperties + : webProperties.getResources(); this.mvcProperties = mvcProperties; this.beanFactory = beanFactory; this.messageConvertersProvider = messageConvertersProvider; @@ -210,6 +221,11 @@ public class WebMvcAutoConfiguration { this.mvcProperties.checkConfiguration(); } + @Override + public void setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + } + @Override public void configureMessageConverters(List> converters) { this.messageConvertersProvider @@ -312,6 +328,49 @@ public class WebMvcAutoConfiguration { ApplicationConversionService.addBeans(registry, this.beanFactory); } + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + if (!this.resourceProperties.isAddMappings()) { + logger.debug("Default resource handling disabled"); + return; + } + addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); + addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { + registration.addResourceLocations(this.resourceProperties.getStaticLocations()); + if (this.servletContext != null) { + ServletContextResource resource = new ServletContextResource(this.servletContext, SERVLET_LOCATION); + registration.addResourceLocations(resource); + } + }); + } + + private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { + addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations)); + } + + private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, + Consumer customizer) { + if (registry.hasMappingForPattern(pattern)) { + return; + } + ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); + customizer.accept(registration); + registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); + registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); + registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()); + customizeResourceHandlerRegistration(registration); + } + + private Integer getSeconds(Duration cachePeriod) { + return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null; + } + + private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) { + if (this.resourceHandlerRegistrationCustomizer != null) { + this.resourceHandlerRegistrationCustomizer.customize(registration); + } + } + @Bean @ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class }) @ConditionalOnMissingFilterBean(RequestContextFilter.class) @@ -328,8 +387,6 @@ public class WebMvcAutoConfiguration { @EnableConfigurationProperties(WebProperties.class) public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware { - private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); - private final Resources resourceProperties; private final WebMvcProperties mvcProperties; @@ -340,8 +397,6 @@ public class WebMvcAutoConfiguration { private final WebMvcRegistrations mvcRegistrations; - private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; - private ResourceLoader resourceLoader; @SuppressWarnings("deprecation") @@ -356,7 +411,6 @@ public class WebMvcAutoConfiguration { this.mvcProperties = mvcProperties; this.webProperties = webProperties; this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique(); - this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable(); this.beanFactory = beanFactory; } @@ -396,50 +450,6 @@ public class WebMvcAutoConfiguration { resourceUrlProvider); } - @Override - protected void addResourceHandlers(ResourceHandlerRegistry registry) { - super.addResourceHandlers(registry); - if (!this.resourceProperties.isAddMappings()) { - logger.debug("Default resource handling disabled"); - return; - } - ServletContext servletContext = getServletContext(); - addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/"); - addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(), (registration) -> { - registration.addResourceLocations(this.resourceProperties.getStaticLocations()); - if (servletContext != null) { - registration.addResourceLocations(new ServletContextResource(servletContext, SERVLET_LOCATION)); - } - }); - } - - private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) { - addResourceHandler(registry, pattern, (registration) -> registration.addResourceLocations(locations)); - } - - private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, - Consumer customizer) { - if (registry.hasMappingForPattern(pattern)) { - return; - } - ResourceHandlerRegistration registration = registry.addResourceHandler(pattern); - customizer.accept(registration); - registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod())); - registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl()); - registration.setUseLastModified(this.resourceProperties.getCache().isUseLastModified()); - customizeResourceHandlerRegistration(registration); - } - - private Integer getSeconds(Duration cachePeriod) { - return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null; - } - - private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) { - if (this.resourceHandlerRegistrationCustomizer != null) { - this.resourceHandlerRegistrationCustomizer.customize(registration); - } - } - @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 5de65c40be..f4dc60af7b 100644 --- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -32,6 +32,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; +import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.ValidatorFactory; @@ -52,9 +53,11 @@ import org.springframework.boot.context.properties.IncompatibleConfigurationExce import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext; import org.springframework.boot.test.context.runner.ContextConsumer; import org.springframework.boot.test.context.runner.WebApplicationContextRunner; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizerBeanPostProcessor; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; +import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext; import org.springframework.boot.web.servlet.filter.OrderedFormContentFilter; import org.springframework.boot.web.servlet.server.ServletWebServerFactory; import org.springframework.context.ApplicationContext; @@ -82,6 +85,7 @@ import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.ParameterContentNegotiationStrategy; import org.springframework.web.bind.support.ConfigurableWebBindingInitializer; import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import org.springframework.web.filter.FormContentFilter; import org.springframework.web.filter.HiddenHttpMethodFilter; @@ -99,6 +103,7 @@ import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer; import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; @@ -1005,6 +1010,25 @@ class WebMvcAutoConfigurationTests { (handler) -> assertThat(handler.isUseLastModified()).isFalse())); } + @Test // gh-25743 + void addResourceHandlersAppliesToChildAndParentContext() { + try (AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext()) { + context.register(WebMvcAutoConfiguration.class, DispatcherServletAutoConfiguration.class, + HttpMessageConvertersAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class, + ResourceHandlersWithChildAndParentContextConfiguration.class); + context.refresh(); + SimpleUrlHandlerMapping resourceHandlerMapping = context.getBean("resourceHandlerMapping", + SimpleUrlHandlerMapping.class); + DispatcherServlet extraDispatcherServlet = context.getBean("extraDispatcherServlet", + DispatcherServlet.class); + SimpleUrlHandlerMapping extraResourceHandlerMapping = extraDispatcherServlet.getWebApplicationContext() + .getBean("resourceHandlerMapping", SimpleUrlHandlerMapping.class); + assertThat(resourceHandlerMapping).isNotSameAs(extraResourceHandlerMapping); + assertThat(resourceHandlerMapping.getUrlMap()).containsKey("/**"); + assertThat(extraResourceHandlerMapping.getUrlMap()).containsKey("/**"); + } + } + private void assertResourceHttpRequestHandler(AssertableWebApplicationContext context, Consumer handlerConsumer) { Map handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class)); @@ -1459,7 +1483,6 @@ class WebMvcAutoConfigurationTests { @Override public void setThemeName(HttpServletRequest request, HttpServletResponse response, String themeName) { - } } @@ -1479,4 +1502,49 @@ class WebMvcAutoConfigurationTests { } + @Configuration(proxyBeanMethods = false) + static class ResourceHandlersWithChildAndParentContextConfiguration { + + @Bean + TomcatServletWebServerFactory webServerFactory() { + return new TomcatServletWebServerFactory(0); + } + + @Bean + ServletRegistrationBean additionalDispatcherServlet(DispatcherServlet extraDispatcherServlet) { + ServletRegistrationBean registration = new ServletRegistrationBean<>(extraDispatcherServlet, "/extra/*"); + registration.setName("additionalDispatcherServlet"); + registration.setLoadOnStartup(1); + return registration; + } + + @Bean + private DispatcherServlet extraDispatcherServlet() throws ServletException { + DispatcherServlet dispatcherServlet = new DispatcherServlet(); + AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext(); + applicationContext.register(ResourceHandlersWithChildAndParentContextChildConfiguration.class); + dispatcherServlet.setApplicationContext(applicationContext); + return dispatcherServlet; + } + + } + + @Configuration(proxyBeanMethods = false) + @EnableWebMvc + static class ResourceHandlersWithChildAndParentContextChildConfiguration { + + @Bean + WebMvcConfigurer myConfigurer() { + return new WebMvcConfigurer() { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/testtesttest"); + } + + }; + } + + } + }