Commit 9dd3fb70 authored by Brian Clozel's avatar Brian Clozel

Remove Servlet-specific static locations

This commit removes the Servlet root context from the default values for
the `spring.resources.static-locations` configuration property. Servlet
and non-Servlet applications are sharing this property.

The Servlet root context is automatically configured as a resource
location for Spring MVC based applications.

Closes gh-9240
parent eb4a9d87
......@@ -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<Resource> resolveFaviconLocations() {
List<Resource> 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;
}
......
......@@ -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<HttpMessageConverter<?>> 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<Resource> 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<Resource> resolveFaviconLocations() {
String[] resourceLocations = getResourceLocations(this.resourceProperties.getStaticLocations());
List<Resource> 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<Resource> 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");
......
......@@ -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
......
......@@ -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
......
......@@ -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 databases 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 Guavas 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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment