Use WebMvcConfigurer to add resource handlers

Move resource handler auto-configuration logic back to the
`WebMvcConfigurer` so that they also get applied to child contexts.

Closes gh-25743
pull/27078/head
Phillip Webb 4 years ago
parent ee76d6038c
commit 004363ceaf

@ -17,11 +17,10 @@
package org.springframework.boot.autoconfigure.web.servlet;
import java.time.Duration;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
@ -54,6 +53,7 @@ import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy;
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigurationResourceHandlerRegistry.RegistrationType;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.convert.ApplicationConversionService;
@ -76,6 +76,7 @@ import org.springframework.format.FormatterRegistry;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.PathMatcher;
import org.springframework.validation.DefaultMessageCodesResolver;
@ -110,6 +111,7 @@ import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
@ -184,6 +186,10 @@ public class WebMvcAutoConfiguration {
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final ResourceProperties resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
@ -194,13 +200,14 @@ public class WebMvcAutoConfiguration {
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
public WebMvcAutoConfigurationAdapter(WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
@ -321,6 +328,39 @@ 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(),
this.resourceProperties.getStaticLocations());
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = AutoConfigurationResourceHandlerRegistry
.addResourceHandler(registry, RegistrationType.AUTO_CONFIGURATION, pattern);
registration.addResourceLocations(locations);
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
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)
@ -336,31 +376,22 @@ public class WebMvcAutoConfiguration {
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
private final ResourceProperties resourceProperties;
private final WebMvcProperties mvcProperties;
private final WebMvcRegistrations mvcRegistrations;
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
private ResourceLoader resourceLoader;
private final ListableBeanFactory beanFactory;
private final Set<String> autoConfiguredResourceHandlers = new HashSet<>();
public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ListableBeanFactory beanFactory) {
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.beanFactory = beanFactory;
}
@ -402,70 +433,28 @@ public class WebMvcAutoConfiguration {
@Bean
@Override
public HandlerMapping resourceHandlerMapping(UrlPathHelper urlPathHelper, PathMatcher pathMatcher,
ContentNegotiationManager contentNegotiationManager, FormattingConversionService conversionService,
ResourceUrlProvider resourceUrlProvider) {
HandlerMapping mapping = super.resourceHandlerMapping(urlPathHelper, pathMatcher, contentNegotiationManager,
conversionService, resourceUrlProvider);
if (mapping instanceof SimpleUrlHandlerMapping) {
addServletContextResourceHandlerMapping((SimpleUrlHandlerMapping) mapping);
public HandlerMapping resourceHandlerMapping(@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
Assert.state(getApplicationContext() != null, "No ApplicationContext set");
Assert.state(getServletContext() != null, "No ServletContext set");
AutoConfigurationResourceHandlerRegistry registry = new AutoConfigurationResourceHandlerRegistry(
getApplicationContext(), getServletContext(), contentNegotiationManager, urlPathHelper,
this.mvcProperties);
addResourceHandlers(registry);
AbstractHandlerMapping mapping = registry.getHandlerMapping();
if (mapping == null) {
return null;
}
mapping.setPathMatcher(pathMatcher);
mapping.setUrlPathHelper(urlPathHelper);
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
private void addServletContextResourceHandlerMapping(SimpleUrlHandlerMapping mapping) {
Map<String, ?> urlMap = mapping.getUrlMap();
String pattern = this.mvcProperties.getStaticPathPattern();
Object handler = urlMap.get(pattern);
if (handler instanceof ResourceHttpRequestHandler
&& this.autoConfiguredResourceHandlers.contains(pattern)) {
addServletContextResourceHandlerMapping((ResourceHttpRequestHandler) handler);
}
}
private void addServletContextResourceHandlerMapping(ResourceHttpRequestHandler handler) {
ServletContext servletContext = getServletContext();
if (servletContext != null) {
List<Resource> locations = handler.getLocations();
locations.add(new ServletContextResource(servletContext, SERVLET_LOCATION));
}
}
@Override
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
super.addResourceHandlers(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(),
this.resourceProperties.getStaticLocations());
}
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
if (registry.hasMappingForPattern(pattern)) {
return;
}
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
registration.addResourceLocations(locations);
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
customizeResourceHandlerRegistration(registration);
this.autoConfiguredResourceHandlers.add(pattern);
}
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) {
@ -677,4 +666,84 @@ public class WebMvcAutoConfiguration {
}
/**
* {@link ResourceHandlerRegistry} that tracks auto-configuration and when appropriate
* adds the {@link ServletContextResource} for {@code /}.
*/
static class AutoConfigurationResourceHandlerRegistry
extends org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry {
private final ServletContext servletContext;
private final WebMvcProperties mvcProperties;
private final Map<String, RegistrationType> registrations = new LinkedHashMap<>();
AutoConfigurationResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
ContentNegotiationManager contentNegotiationManager, UrlPathHelper pathHelper,
WebMvcProperties mvcProperties) {
super(applicationContext, servletContext, contentNegotiationManager, pathHelper);
this.servletContext = servletContext;
this.mvcProperties = mvcProperties;
}
@Override
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
return addResourceHandler(RegistrationType.STANDARD, pathPatterns);
}
ResourceHandlerRegistration addResourceHandler(RegistrationType type, String... pathPatterns) {
for (String pathPattern : pathPatterns) {
this.registrations.put(pathPattern, type);
}
return super.addResourceHandler(pathPatterns);
}
@Override
protected AbstractHandlerMapping getHandlerMapping() {
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) super.getHandlerMapping();
reconfigure(mapping);
return mapping;
}
private void reconfigure(SimpleUrlHandlerMapping mapping) {
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (this.registrations.get(staticPathPattern) == RegistrationType.AUTO_CONFIGURATION) {
addServletContextResourceHandlerMapping(mapping, staticPathPattern);
}
}
private void addServletContextResourceHandlerMapping(SimpleUrlHandlerMapping mapping,
String staticPathPattern) {
Object handler = mapping.getUrlMap().get(staticPathPattern);
if (handler instanceof ResourceHttpRequestHandler) {
addServletContextResourceHandlerMapping((ResourceHttpRequestHandler) handler);
}
}
private void addServletContextResourceHandlerMapping(ResourceHttpRequestHandler handler) {
if (this.servletContext != null) {
List<Resource> locations = handler.getLocations();
locations.add(new ServletContextResource(this.servletContext, SERVLET_LOCATION));
}
}
static ResourceHandlerRegistration addResourceHandler(ResourceHandlerRegistry registry, RegistrationType type,
String... pathPatterns) {
if (registry instanceof AutoConfigurationResourceHandlerRegistry) {
return ((AutoConfigurationResourceHandlerRegistry) registry).addResourceHandler(type, pathPatterns);
}
return registry.addResourceHandler(pathPatterns);
}
enum RegistrationType {
STANDARD,
AUTO_CONFIGURATION
}
}
}

@ -33,6 +33,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;
@ -50,9 +51,11 @@ import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguratio
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;
@ -81,6 +84,7 @@ import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
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;
@ -95,6 +99,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;
@ -898,6 +903,25 @@ class WebMvcAutoConfigurationTests {
});
}
@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 assertCacheControl(AssertableWebApplicationContext context) {
Map<String, Object> handlerMap = getHandlerMap(context.getBean("resourceHandlerMapping", HandlerMapping.class));
assertThat(handlerMap).hasSize(2);
@ -1330,4 +1354,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");
}
};
}
}
}

Loading…
Cancel
Save