diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java index 2e6a52c0b34..d3cd31efe58 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java @@ -16,15 +16,7 @@ package org.springframework.boot.autoconfigure.web; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.context.ResourceLoaderAware; -import org.springframework.core.io.ClassPathResource; -import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; /** * Properties used to configure resource handling. @@ -36,30 +28,17 @@ import org.springframework.core.io.ResourceLoader; * @since 1.1.0 */ @ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false) -public class ResourceProperties implements ResourceLoaderAware { - - private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" }; +public class ResourceProperties { private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" }; - private static final String[] RESOURCE_LOCATIONS; - - static { - RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length - + SERVLET_RESOURCE_LOCATIONS.length]; - System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0, - SERVLET_RESOURCE_LOCATIONS.length); - System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, - SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length); - } - /** * Locations of static resources. Defaults to classpath:[/META-INF/resources/, - * /resources/, /static/, /public/] plus context:/ (the root of the servlet context). + * /resources/, /static/, /public/]. */ - private String[] staticLocations = RESOURCE_LOCATIONS; + private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS; /** * Cache period for the resources served by the resource handler, in seconds. @@ -73,12 +52,6 @@ public class ResourceProperties implements ResourceLoaderAware { private final Chain chain = new Chain(); - private ResourceLoader resourceLoader; - - @Override - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; - } public String[] getStaticLocations() { return this.staticLocations; @@ -97,45 +70,6 @@ public class ResourceProperties implements ResourceLoaderAware { return normalized; } - public Resource getWelcomePage() { - for (String location : getStaticWelcomePageLocations()) { - Resource resource = this.resourceLoader.getResource(location); - try { - if (resource.exists()) { - resource.getURL(); - return resource; - } - } - catch (Exception ex) { - // Ignore - } - } - return null; - } - - private String[] getStaticWelcomePageLocations() { - String[] result = new String[this.staticLocations.length]; - for (int i = 0; i < result.length; i++) { - String location = this.staticLocations[i]; - if (!location.endsWith("/")) { - location = location + "/"; - } - result[i] = location + "index.html"; - } - return result; - } - - public List resolveFaviconLocations() { - List locations = new ArrayList<>(this.staticLocations.length + 1); - if (this.resourceLoader != null) { - for (String location : this.staticLocations) { - locations.add(this.resourceLoader.getResource(location)); - } - } - locations.add(new ClassPathResource("/")); - return Collections.unmodifiableList(locations); - } - public Integer getCachePeriod() { return this.cachePeriod; } diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java index 7d4b789eddd..9102d297c41 100644 --- a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration.java @@ -16,6 +16,8 @@ package org.springframework.boot.autoconfigure.web.servlet; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -23,6 +25,7 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import javax.servlet.Servlet; import javax.servlet.http.HttpServletRequest; @@ -54,6 +57,7 @@ import org.springframework.boot.context.properties.EnableConfigurationProperties import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter; import org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter; import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter; +import org.springframework.context.ResourceLoaderAware; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -62,7 +66,9 @@ import org.springframework.context.annotation.Primary; import org.springframework.core.Ordered; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.GenericConverter; +import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; import org.springframework.format.Formatter; import org.springframework.format.FormatterRegistry; import org.springframework.format.datetime.DateFormatter; @@ -140,6 +146,8 @@ public class WebMvcAutoConfiguration { public static final String DEFAULT_SUFFIX = ""; + private static final String[] SERVLET_LOCATIONS = { "/" }; + @Bean @ConditionalOnMissingBean(HiddenHttpMethodFilter.class) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { @@ -158,7 +166,7 @@ public class WebMvcAutoConfiguration { @Configuration @Import(EnableWebMvcConfiguration.class) @EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class }) - public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer { + public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware { private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class); @@ -172,6 +180,8 @@ public class WebMvcAutoConfiguration { final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer; + private ResourceLoader resourceLoader; + public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties, WebMvcProperties mvcProperties, ListableBeanFactory beanFactory, @Lazy HttpMessageConverters messageConverters, @@ -184,6 +194,11 @@ public class WebMvcAutoConfiguration { .getIfAvailable(); } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + @Override public void configureMessageConverters(List> converters) { converters.addAll(this.messageConverters.getConverters()); @@ -302,18 +317,43 @@ public class WebMvcAutoConfiguration { customizeResourceHandlerRegistration( registry.addResourceHandler(staticPathPattern) .addResourceLocations( - this.resourceProperties.getStaticLocations()) + getResourceLocations(this.resourceProperties.getStaticLocations())) .setCachePeriod(cachePeriod)); } } @Bean - public WelcomePageHandlerMapping welcomePageHandlerMapping( - ResourceProperties resourceProperties) { - return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), + public WelcomePageHandlerMapping welcomePageHandlerMapping() { + return new WelcomePageHandlerMapping(getWelcomePage(), this.mvcProperties.getStaticPathPattern()); } + static String[] getResourceLocations(String[] staticLocations) { + String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length]; + System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length); + System.arraycopy(SERVLET_LOCATIONS, 0, locations, + staticLocations.length, SERVLET_LOCATIONS.length); + return locations; + } + + private Optional getWelcomePage() { + return Arrays.stream(getResourceLocations(this.resourceProperties.getStaticLocations())) + .map(location -> this.resourceLoader.getResource(location + "index.html")) + .filter(resource -> { + try { + if (resource.exists()) { + resource.getURL(); + return true; + } + } + catch (Exception ex) { + // Ignore + } + return false; + }) + .findFirst(); + } + private void customizeResourceHandlerRegistration( ResourceHandlerRegistration registration) { if (this.resourceHandlerRegistrationCustomizer != null) { @@ -331,14 +371,21 @@ public class WebMvcAutoConfiguration { @Configuration @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true) - public static class FaviconConfiguration { + public static class FaviconConfiguration implements ResourceLoaderAware { private final ResourceProperties resourceProperties; + private ResourceLoader resourceLoader; + public FaviconConfiguration(ResourceProperties resourceProperties) { this.resourceProperties = resourceProperties; } + @Override + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; + } + @Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); @@ -351,11 +398,19 @@ public class WebMvcAutoConfiguration { @Bean public ResourceHttpRequestHandler faviconRequestHandler() { ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); - requestHandler - .setLocations(this.resourceProperties.resolveFaviconLocations()); + requestHandler.setLocations(resolveFaviconLocations()); return requestHandler; } + private List resolveFaviconLocations() { + String[] resourceLocations = getResourceLocations(this.resourceProperties.getStaticLocations()); + List locations = new ArrayList<>(resourceLocations.length + 1); + Arrays.stream(resourceLocations) + .forEach(location -> locations.add(this.resourceLoader.getResource(location))); + locations.add(new ClassPathResource("/")); + return Collections.unmodifiableList(locations); + } + } } @@ -546,9 +601,9 @@ public class WebMvcAutoConfiguration { private static final Log logger = LogFactory .getLog(WelcomePageHandlerMapping.class); - private WelcomePageHandlerMapping(Resource welcomePage, + private WelcomePageHandlerMapping(Optional welcomePage, String staticPathPattern) { - if (welcomePage != null && "/**".equals(staticPathPattern)) { + if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) { logger.info("Adding welcome page: " + welcomePage); ParameterizableViewController controller = new ParameterizableViewController(); controller.setViewName("forward:index.html"); diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java index 8d79345bae7..9cb203cd271 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java @@ -127,7 +127,7 @@ public class WebFluxAutoConfigurationTests { SimpleUrlHandlerMapping.class); assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**"); - assertThat(staticHandler.getLocations()).hasSize(5); + assertThat(staticHandler.getLocations()).hasSize(4); assertThat(hm.getUrlMap().get("/webjars/**")) .isInstanceOf(ResourceWebHandler.class); ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap() @@ -146,7 +146,7 @@ public class WebFluxAutoConfigurationTests { .isInstanceOf(ResourceWebHandler.class); ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap() .get("/static/**"); - assertThat(staticHandler.getLocations()).hasSize(5); + assertThat(staticHandler.getLocations()).hasSize(4); } @Test diff --git a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java index 6fa98743ea8..34869163965 100644 --- a/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java +++ b/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java @@ -442,7 +442,7 @@ public class WebMvcAutoConfigurationTests { .withPropertyValues("spring.resources.static-locations=classpath:/static") .run((context) -> assertThat( getFaviconMappingLocations(context).get("/**/favicon.ico")) - .hasSize(2)); + .hasSize(3)); } @Test diff --git a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc index 6d582a7faf6..7361be3220f 100644 --- a/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc +++ b/spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc @@ -1900,10 +1900,11 @@ can be achieved as follows: ---- You can also customize the static resource locations using -`spring.resources.static-locations` (replacing the default values with a list of directory -locations). If you do this the default welcome page detection will switch to your custom -locations, so if there is an `index.html` in any of your locations on startup, it will be -the home page of the application. +`spring.resources.static-locations` (replacing the default values with a list +of directory locations). The root Servlet context path `"/"` will be automatically +added as a location as well. If you do this the default welcome page detection will +switch to your custom locations, so if there is an `index.html` in any of your locations +on startup, it will be the home page of the application. In addition to the '`standard`' static resource locations above, a special case is made for http://www.webjars.org/[Webjars content]. Any resources with a path in `+/webjars/**+` @@ -2249,7 +2250,7 @@ Unlike Spring MVC, it does not require the Servlet API, is fully asynchronous an non-blocking, and implements the http://www.reactive-streams.org/[Reactive Streams] specification through http://projectreactor.io/[the Reactor project]. -Spring WebFlux comes in two flavors — the annotation-based one is quite close to the +Spring WebFlux comes in two flavors; the annotation-based one is quite close to the Spring MVC model we know: [source,java,indent=0] @@ -3066,7 +3067,7 @@ auto-configured. In this example it's pulled in transitively via `spring-boot-starter-data-jpa`. TIP: If, for whatever reason, you do configure the connection URL for an embedded -database, care should be taken to ensure that the database’s automatic shutdown is +database, care should be taken to ensure that the database's automatic shutdown is disabled. If you're using H2 you should use `DB_CLOSE_ON_EXIT=FALSE` to do so. If you're using HSQLDB, you should ensure that `shutdown=true` is not used. Disabling the database's automatic shutdown allows Spring Boot to control when the database is closed, thereby @@ -4573,7 +4574,7 @@ recommend to keep this setting enabled if you create your own `RedisCacheManager [[boot-features-caching-provider-caffeine]] ==== Caffeine -Caffeine is a Java 8 rewrite of Guava’s cache that supersede the Guava support. If +Caffeine is a Java 8 rewrite of Guava's cache that supersede the Guava support. If Caffeine is present, a `CaffeineCacheManager` (provided by the `spring-boot-starter-cache` '`Starter`') is auto-configured. Caches can be created on startup using the `spring.cache.cache-names` property and customized by one of the