Skip to content

Commit 004363c

Browse files
committed
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
1 parent ee76d60 commit 004363c

File tree

2 files changed

+213
-75
lines changed

2 files changed

+213
-75
lines changed

spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java

Lines changed: 144 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,10 @@
1717
package org.springframework.boot.autoconfigure.web.servlet;
1818

1919
import java.time.Duration;
20-
import java.util.HashSet;
20+
import java.util.LinkedHashMap;
2121
import java.util.List;
2222
import java.util.ListIterator;
2323
import java.util.Map;
24-
import java.util.Set;
2524

2625
import javax.servlet.Servlet;
2726
import javax.servlet.ServletContext;
@@ -54,6 +53,7 @@
5453
import org.springframework.boot.autoconfigure.web.ResourceProperties.Strategy;
5554
import org.springframework.boot.autoconfigure.web.format.DateTimeFormatters;
5655
import org.springframework.boot.autoconfigure.web.format.WebConversionService;
56+
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration.AutoConfigurationResourceHandlerRegistry.RegistrationType;
5757
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.Format;
5858
import org.springframework.boot.context.properties.EnableConfigurationProperties;
5959
import org.springframework.boot.convert.ApplicationConversionService;
@@ -76,6 +76,7 @@
7676
import org.springframework.format.support.FormattingConversionService;
7777
import org.springframework.http.MediaType;
7878
import org.springframework.http.converter.HttpMessageConverter;
79+
import org.springframework.util.Assert;
7980
import org.springframework.util.ClassUtils;
8081
import org.springframework.util.PathMatcher;
8182
import org.springframework.validation.DefaultMessageCodesResolver;
@@ -110,6 +111,7 @@
110111
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
111112
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
112113
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
114+
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
113115
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
114116
import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver;
115117
import org.springframework.web.servlet.i18n.FixedLocaleResolver;
@@ -184,6 +186,10 @@ public OrderedFormContentFilter formContentFilter() {
184186
@Order(0)
185187
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
186188

189+
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
190+
191+
private final ResourceProperties resourceProperties;
192+
187193
private final WebMvcProperties mvcProperties;
188194

189195
private final ListableBeanFactory beanFactory;
@@ -194,13 +200,14 @@ public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
194200

195201
private final ObjectProvider<ServletRegistrationBean<?>> servletRegistrations;
196202

197-
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
203+
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
198204

199-
public WebMvcAutoConfigurationAdapter(WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
200-
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
205+
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties,
206+
ListableBeanFactory beanFactory, ObjectProvider<HttpMessageConverters> messageConvertersProvider,
201207
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
202208
ObjectProvider<DispatcherServletPath> dispatcherServletPath,
203209
ObjectProvider<ServletRegistrationBean<?>> servletRegistrations) {
210+
this.resourceProperties = resourceProperties;
204211
this.mvcProperties = mvcProperties;
205212
this.beanFactory = beanFactory;
206213
this.messageConvertersProvider = messageConvertersProvider;
@@ -321,6 +328,39 @@ public void addFormatters(FormatterRegistry registry) {
321328
ApplicationConversionService.addBeans(registry, this.beanFactory);
322329
}
323330

331+
@Override
332+
public void addResourceHandlers(ResourceHandlerRegistry registry) {
333+
if (!this.resourceProperties.isAddMappings()) {
334+
logger.debug("Default resource handling disabled");
335+
return;
336+
}
337+
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
338+
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
339+
this.resourceProperties.getStaticLocations());
340+
}
341+
342+
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
343+
if (registry.hasMappingForPattern(pattern)) {
344+
return;
345+
}
346+
ResourceHandlerRegistration registration = AutoConfigurationResourceHandlerRegistry
347+
.addResourceHandler(registry, RegistrationType.AUTO_CONFIGURATION, pattern);
348+
registration.addResourceLocations(locations);
349+
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
350+
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
351+
customizeResourceHandlerRegistration(registration);
352+
}
353+
354+
private Integer getSeconds(Duration cachePeriod) {
355+
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
356+
}
357+
358+
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
359+
if (this.resourceHandlerRegistrationCustomizer != null) {
360+
this.resourceHandlerRegistrationCustomizer.customize(registration);
361+
}
362+
}
363+
324364
@Bean
325365
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
326366
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
@@ -336,31 +376,22 @@ public static RequestContextFilter requestContextFilter() {
336376
@Configuration(proxyBeanMethods = false)
337377
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
338378

339-
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
340-
341379
private final ResourceProperties resourceProperties;
342380

343381
private final WebMvcProperties mvcProperties;
344382

345383
private final WebMvcRegistrations mvcRegistrations;
346384

347-
private final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
348-
349385
private ResourceLoader resourceLoader;
350386

351387
private final ListableBeanFactory beanFactory;
352388

353-
private final Set<String> autoConfiguredResourceHandlers = new HashSet<>();
354-
355389
public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
356390
ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
357-
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider,
358-
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
359-
ListableBeanFactory beanFactory) {
391+
ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
360392
this.resourceProperties = resourceProperties;
361393
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
362394
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
363-
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
364395
this.beanFactory = beanFactory;
365396
}
366397

@@ -402,70 +433,28 @@ public RequestMappingHandlerMapping requestMappingHandlerMapping(
402433

403434
@Bean
404435
@Override
405-
public HandlerMapping resourceHandlerMapping(UrlPathHelper urlPathHelper, PathMatcher pathMatcher,
406-
ContentNegotiationManager contentNegotiationManager, FormattingConversionService conversionService,
407-
ResourceUrlProvider resourceUrlProvider) {
408-
HandlerMapping mapping = super.resourceHandlerMapping(urlPathHelper, pathMatcher, contentNegotiationManager,
409-
conversionService, resourceUrlProvider);
410-
if (mapping instanceof SimpleUrlHandlerMapping) {
411-
addServletContextResourceHandlerMapping((SimpleUrlHandlerMapping) mapping);
436+
public HandlerMapping resourceHandlerMapping(@Qualifier("mvcUrlPathHelper") UrlPathHelper urlPathHelper,
437+
@Qualifier("mvcPathMatcher") PathMatcher pathMatcher,
438+
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
439+
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
440+
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
441+
Assert.state(getApplicationContext() != null, "No ApplicationContext set");
442+
Assert.state(getServletContext() != null, "No ServletContext set");
443+
AutoConfigurationResourceHandlerRegistry registry = new AutoConfigurationResourceHandlerRegistry(
444+
getApplicationContext(), getServletContext(), contentNegotiationManager, urlPathHelper,
445+
this.mvcProperties);
446+
addResourceHandlers(registry);
447+
AbstractHandlerMapping mapping = registry.getHandlerMapping();
448+
if (mapping == null) {
449+
return null;
412450
}
451+
mapping.setPathMatcher(pathMatcher);
452+
mapping.setUrlPathHelper(urlPathHelper);
453+
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
454+
mapping.setCorsConfigurations(getCorsConfigurations());
413455
return mapping;
414456
}
415457

416-
private void addServletContextResourceHandlerMapping(SimpleUrlHandlerMapping mapping) {
417-
Map<String, ?> urlMap = mapping.getUrlMap();
418-
String pattern = this.mvcProperties.getStaticPathPattern();
419-
Object handler = urlMap.get(pattern);
420-
if (handler instanceof ResourceHttpRequestHandler
421-
&& this.autoConfiguredResourceHandlers.contains(pattern)) {
422-
addServletContextResourceHandlerMapping((ResourceHttpRequestHandler) handler);
423-
}
424-
}
425-
426-
private void addServletContextResourceHandlerMapping(ResourceHttpRequestHandler handler) {
427-
ServletContext servletContext = getServletContext();
428-
if (servletContext != null) {
429-
List<Resource> locations = handler.getLocations();
430-
locations.add(new ServletContextResource(servletContext, SERVLET_LOCATION));
431-
}
432-
}
433-
434-
@Override
435-
protected void addResourceHandlers(ResourceHandlerRegistry registry) {
436-
super.addResourceHandlers(registry);
437-
if (!this.resourceProperties.isAddMappings()) {
438-
logger.debug("Default resource handling disabled");
439-
return;
440-
}
441-
addResourceHandler(registry, "/webjars/**", "classpath:/META-INF/resources/webjars/");
442-
addResourceHandler(registry, this.mvcProperties.getStaticPathPattern(),
443-
this.resourceProperties.getStaticLocations());
444-
445-
}
446-
447-
private void addResourceHandler(ResourceHandlerRegistry registry, String pattern, String... locations) {
448-
if (registry.hasMappingForPattern(pattern)) {
449-
return;
450-
}
451-
ResourceHandlerRegistration registration = registry.addResourceHandler(pattern);
452-
registration.addResourceLocations(locations);
453-
registration.setCachePeriod(getSeconds(this.resourceProperties.getCache().getPeriod()));
454-
registration.setCacheControl(this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl());
455-
customizeResourceHandlerRegistration(registration);
456-
this.autoConfiguredResourceHandlers.add(pattern);
457-
}
458-
459-
private Integer getSeconds(Duration cachePeriod) {
460-
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
461-
}
462-
463-
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
464-
if (this.resourceHandlerRegistrationCustomizer != null) {
465-
this.resourceHandlerRegistrationCustomizer.customize(registration);
466-
}
467-
}
468-
469458
@Bean
470459
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext,
471460
FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
@@ -677,4 +666,84 @@ public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
677666

678667
}
679668

669+
/**
670+
* {@link ResourceHandlerRegistry} that tracks auto-configuration and when appropriate
671+
* adds the {@link ServletContextResource} for {@code /}.
672+
*/
673+
static class AutoConfigurationResourceHandlerRegistry
674+
extends org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry {
675+
676+
private final ServletContext servletContext;
677+
678+
private final WebMvcProperties mvcProperties;
679+
680+
private final Map<String, RegistrationType> registrations = new LinkedHashMap<>();
681+
682+
AutoConfigurationResourceHandlerRegistry(ApplicationContext applicationContext, ServletContext servletContext,
683+
ContentNegotiationManager contentNegotiationManager, UrlPathHelper pathHelper,
684+
WebMvcProperties mvcProperties) {
685+
super(applicationContext, servletContext, contentNegotiationManager, pathHelper);
686+
this.servletContext = servletContext;
687+
this.mvcProperties = mvcProperties;
688+
}
689+
690+
@Override
691+
public ResourceHandlerRegistration addResourceHandler(String... pathPatterns) {
692+
return addResourceHandler(RegistrationType.STANDARD, pathPatterns);
693+
}
694+
695+
ResourceHandlerRegistration addResourceHandler(RegistrationType type, String... pathPatterns) {
696+
for (String pathPattern : pathPatterns) {
697+
this.registrations.put(pathPattern, type);
698+
}
699+
return super.addResourceHandler(pathPatterns);
700+
}
701+
702+
@Override
703+
protected AbstractHandlerMapping getHandlerMapping() {
704+
SimpleUrlHandlerMapping mapping = (SimpleUrlHandlerMapping) super.getHandlerMapping();
705+
reconfigure(mapping);
706+
return mapping;
707+
}
708+
709+
private void reconfigure(SimpleUrlHandlerMapping mapping) {
710+
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
711+
if (this.registrations.get(staticPathPattern) == RegistrationType.AUTO_CONFIGURATION) {
712+
addServletContextResourceHandlerMapping(mapping, staticPathPattern);
713+
}
714+
}
715+
716+
private void addServletContextResourceHandlerMapping(SimpleUrlHandlerMapping mapping,
717+
String staticPathPattern) {
718+
Object handler = mapping.getUrlMap().get(staticPathPattern);
719+
if (handler instanceof ResourceHttpRequestHandler) {
720+
addServletContextResourceHandlerMapping((ResourceHttpRequestHandler) handler);
721+
}
722+
}
723+
724+
private void addServletContextResourceHandlerMapping(ResourceHttpRequestHandler handler) {
725+
if (this.servletContext != null) {
726+
List<Resource> locations = handler.getLocations();
727+
locations.add(new ServletContextResource(this.servletContext, SERVLET_LOCATION));
728+
}
729+
}
730+
731+
static ResourceHandlerRegistration addResourceHandler(ResourceHandlerRegistry registry, RegistrationType type,
732+
String... pathPatterns) {
733+
if (registry instanceof AutoConfigurationResourceHandlerRegistry) {
734+
return ((AutoConfigurationResourceHandlerRegistry) registry).addResourceHandler(type, pathPatterns);
735+
}
736+
return registry.addResourceHandler(pathPatterns);
737+
}
738+
739+
enum RegistrationType {
740+
741+
STANDARD,
742+
743+
AUTO_CONFIGURATION
744+
745+
}
746+
747+
}
748+
680749
}

0 commit comments

Comments
 (0)