Skip to content

Commit 9dd3fb7

Browse files
committed
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
1 parent eb4a9d8 commit 9dd3fb7

File tree

5 files changed

+79
-89
lines changed

5 files changed

+79
-89
lines changed

spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/ResourceProperties.java

Lines changed: 3 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,7 @@
1616

1717
package org.springframework.boot.autoconfigure.web;
1818

19-
import java.util.ArrayList;
20-
import java.util.Collections;
21-
import java.util.List;
22-
2319
import org.springframework.boot.context.properties.ConfigurationProperties;
24-
import org.springframework.context.ResourceLoaderAware;
25-
import org.springframework.core.io.ClassPathResource;
26-
import org.springframework.core.io.Resource;
27-
import org.springframework.core.io.ResourceLoader;
2820

2921
/**
3022
* Properties used to configure resource handling.
@@ -36,30 +28,17 @@
3628
* @since 1.1.0
3729
*/
3830
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
39-
public class ResourceProperties implements ResourceLoaderAware {
40-
41-
private static final String[] SERVLET_RESOURCE_LOCATIONS = { "/" };
31+
public class ResourceProperties {
4232

4333
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = {
4434
"classpath:/META-INF/resources/", "classpath:/resources/",
4535
"classpath:/static/", "classpath:/public/" };
4636

47-
private static final String[] RESOURCE_LOCATIONS;
48-
49-
static {
50-
RESOURCE_LOCATIONS = new String[CLASSPATH_RESOURCE_LOCATIONS.length
51-
+ SERVLET_RESOURCE_LOCATIONS.length];
52-
System.arraycopy(SERVLET_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS, 0,
53-
SERVLET_RESOURCE_LOCATIONS.length);
54-
System.arraycopy(CLASSPATH_RESOURCE_LOCATIONS, 0, RESOURCE_LOCATIONS,
55-
SERVLET_RESOURCE_LOCATIONS.length, CLASSPATH_RESOURCE_LOCATIONS.length);
56-
}
57-
5837
/**
5938
* Locations of static resources. Defaults to classpath:[/META-INF/resources/,
60-
* /resources/, /static/, /public/] plus context:/ (the root of the servlet context).
39+
* /resources/, /static/, /public/].
6140
*/
62-
private String[] staticLocations = RESOURCE_LOCATIONS;
41+
private String[] staticLocations = CLASSPATH_RESOURCE_LOCATIONS;
6342

6443
/**
6544
* Cache period for the resources served by the resource handler, in seconds.
@@ -73,12 +52,6 @@ public class ResourceProperties implements ResourceLoaderAware {
7352

7453
private final Chain chain = new Chain();
7554

76-
private ResourceLoader resourceLoader;
77-
78-
@Override
79-
public void setResourceLoader(ResourceLoader resourceLoader) {
80-
this.resourceLoader = resourceLoader;
81-
}
8255

8356
public String[] getStaticLocations() {
8457
return this.staticLocations;
@@ -97,45 +70,6 @@ private String[] appendSlashIfNecessary(String[] staticLocations) {
9770
return normalized;
9871
}
9972

100-
public Resource getWelcomePage() {
101-
for (String location : getStaticWelcomePageLocations()) {
102-
Resource resource = this.resourceLoader.getResource(location);
103-
try {
104-
if (resource.exists()) {
105-
resource.getURL();
106-
return resource;
107-
}
108-
}
109-
catch (Exception ex) {
110-
// Ignore
111-
}
112-
}
113-
return null;
114-
}
115-
116-
private String[] getStaticWelcomePageLocations() {
117-
String[] result = new String[this.staticLocations.length];
118-
for (int i = 0; i < result.length; i++) {
119-
String location = this.staticLocations[i];
120-
if (!location.endsWith("/")) {
121-
location = location + "/";
122-
}
123-
result[i] = location + "index.html";
124-
}
125-
return result;
126-
}
127-
128-
public List<Resource> resolveFaviconLocations() {
129-
List<Resource> locations = new ArrayList<>(this.staticLocations.length + 1);
130-
if (this.resourceLoader != null) {
131-
for (String location : this.staticLocations) {
132-
locations.add(this.resourceLoader.getResource(location));
133-
}
134-
}
135-
locations.add(new ClassPathResource("/"));
136-
return Collections.unmodifiableList(locations);
137-
}
138-
13973
public Integer getCachePeriod() {
14074
return this.cachePeriod;
14175
}

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

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,16 @@
1616

1717
package org.springframework.boot.autoconfigure.web.servlet;
1818

19+
import java.util.ArrayList;
20+
import java.util.Arrays;
1921
import java.util.Collection;
2022
import java.util.Collections;
2123
import java.util.Date;
2224
import java.util.List;
2325
import java.util.ListIterator;
2426
import java.util.Map;
2527
import java.util.Map.Entry;
28+
import java.util.Optional;
2629

2730
import javax.servlet.Servlet;
2831
import javax.servlet.http.HttpServletRequest;
@@ -54,6 +57,7 @@
5457
import org.springframework.boot.web.servlet.filter.OrderedHiddenHttpMethodFilter;
5558
import org.springframework.boot.web.servlet.filter.OrderedHttpPutFormContentFilter;
5659
import org.springframework.boot.web.servlet.filter.OrderedRequestContextFilter;
60+
import org.springframework.context.ResourceLoaderAware;
5761
import org.springframework.context.annotation.Bean;
5862
import org.springframework.context.annotation.Configuration;
5963
import org.springframework.context.annotation.Import;
@@ -62,7 +66,9 @@
6266
import org.springframework.core.Ordered;
6367
import org.springframework.core.convert.converter.Converter;
6468
import org.springframework.core.convert.converter.GenericConverter;
69+
import org.springframework.core.io.ClassPathResource;
6570
import org.springframework.core.io.Resource;
71+
import org.springframework.core.io.ResourceLoader;
6672
import org.springframework.format.Formatter;
6773
import org.springframework.format.FormatterRegistry;
6874
import org.springframework.format.datetime.DateFormatter;
@@ -140,6 +146,8 @@ public class WebMvcAutoConfiguration {
140146

141147
public static final String DEFAULT_SUFFIX = "";
142148

149+
private static final String[] SERVLET_LOCATIONS = { "/" };
150+
143151
@Bean
144152
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
145153
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
@@ -158,7 +166,7 @@ public OrderedHttpPutFormContentFilter httpPutFormContentFilter() {
158166
@Configuration
159167
@Import(EnableWebMvcConfiguration.class)
160168
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
161-
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
169+
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer, ResourceLoaderAware {
162170

163171
private static final Log logger = LogFactory.getLog(WebMvcConfigurer.class);
164172

@@ -172,6 +180,8 @@ public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
172180

173181
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
174182

183+
private ResourceLoader resourceLoader;
184+
175185
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
176186
WebMvcProperties mvcProperties, ListableBeanFactory beanFactory,
177187
@Lazy HttpMessageConverters messageConverters,
@@ -184,6 +194,11 @@ public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
184194
.getIfAvailable();
185195
}
186196

197+
@Override
198+
public void setResourceLoader(ResourceLoader resourceLoader) {
199+
this.resourceLoader = resourceLoader;
200+
}
201+
187202
@Override
188203
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
189204
converters.addAll(this.messageConverters.getConverters());
@@ -302,18 +317,43 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) {
302317
customizeResourceHandlerRegistration(
303318
registry.addResourceHandler(staticPathPattern)
304319
.addResourceLocations(
305-
this.resourceProperties.getStaticLocations())
320+
getResourceLocations(this.resourceProperties.getStaticLocations()))
306321
.setCachePeriod(cachePeriod));
307322
}
308323
}
309324

310325
@Bean
311-
public WelcomePageHandlerMapping welcomePageHandlerMapping(
312-
ResourceProperties resourceProperties) {
313-
return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(),
326+
public WelcomePageHandlerMapping welcomePageHandlerMapping() {
327+
return new WelcomePageHandlerMapping(getWelcomePage(),
314328
this.mvcProperties.getStaticPathPattern());
315329
}
316330

331+
static String[] getResourceLocations(String[] staticLocations) {
332+
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
333+
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
334+
System.arraycopy(SERVLET_LOCATIONS, 0, locations,
335+
staticLocations.length, SERVLET_LOCATIONS.length);
336+
return locations;
337+
}
338+
339+
private Optional<Resource> getWelcomePage() {
340+
return Arrays.stream(getResourceLocations(this.resourceProperties.getStaticLocations()))
341+
.map(location -> this.resourceLoader.getResource(location + "index.html"))
342+
.filter(resource -> {
343+
try {
344+
if (resource.exists()) {
345+
resource.getURL();
346+
return true;
347+
}
348+
}
349+
catch (Exception ex) {
350+
// Ignore
351+
}
352+
return false;
353+
})
354+
.findFirst();
355+
}
356+
317357
private void customizeResourceHandlerRegistration(
318358
ResourceHandlerRegistration registration) {
319359
if (this.resourceHandlerRegistrationCustomizer != null) {
@@ -331,14 +371,21 @@ public static RequestContextFilter requestContextFilter() {
331371

332372
@Configuration
333373
@ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true)
334-
public static class FaviconConfiguration {
374+
public static class FaviconConfiguration implements ResourceLoaderAware {
335375

336376
private final ResourceProperties resourceProperties;
337377

378+
private ResourceLoader resourceLoader;
379+
338380
public FaviconConfiguration(ResourceProperties resourceProperties) {
339381
this.resourceProperties = resourceProperties;
340382
}
341383

384+
@Override
385+
public void setResourceLoader(ResourceLoader resourceLoader) {
386+
this.resourceLoader = resourceLoader;
387+
}
388+
342389
@Bean
343390
public SimpleUrlHandlerMapping faviconHandlerMapping() {
344391
SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping();
@@ -351,11 +398,19 @@ public SimpleUrlHandlerMapping faviconHandlerMapping() {
351398
@Bean
352399
public ResourceHttpRequestHandler faviconRequestHandler() {
353400
ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler();
354-
requestHandler
355-
.setLocations(this.resourceProperties.resolveFaviconLocations());
401+
requestHandler.setLocations(resolveFaviconLocations());
356402
return requestHandler;
357403
}
358404

405+
private List<Resource> resolveFaviconLocations() {
406+
String[] resourceLocations = getResourceLocations(this.resourceProperties.getStaticLocations());
407+
List<Resource> locations = new ArrayList<>(resourceLocations.length + 1);
408+
Arrays.stream(resourceLocations)
409+
.forEach(location -> locations.add(this.resourceLoader.getResource(location)));
410+
locations.add(new ClassPathResource("/"));
411+
return Collections.unmodifiableList(locations);
412+
}
413+
359414
}
360415

361416
}
@@ -546,9 +601,9 @@ static final class WelcomePageHandlerMapping extends AbstractUrlHandlerMapping {
546601
private static final Log logger = LogFactory
547602
.getLog(WelcomePageHandlerMapping.class);
548603

549-
private WelcomePageHandlerMapping(Resource welcomePage,
604+
private WelcomePageHandlerMapping(Optional<Resource> welcomePage,
550605
String staticPathPattern) {
551-
if (welcomePage != null && "/**".equals(staticPathPattern)) {
606+
if (welcomePage.isPresent() && "/**".equals(staticPathPattern)) {
552607
logger.info("Adding welcome page: " + welcomePage);
553608
ParameterizableViewController controller = new ParameterizableViewController();
554609
controller.setViewName("forward:index.html");

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/reactive/WebFluxAutoConfigurationTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public void shouldRegisterResourceHandlerMapping() throws Exception {
127127
SimpleUrlHandlerMapping.class);
128128
assertThat(hm.getUrlMap().get("/**")).isInstanceOf(ResourceWebHandler.class);
129129
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap().get("/**");
130-
assertThat(staticHandler.getLocations()).hasSize(5);
130+
assertThat(staticHandler.getLocations()).hasSize(4);
131131
assertThat(hm.getUrlMap().get("/webjars/**"))
132132
.isInstanceOf(ResourceWebHandler.class);
133133
ResourceWebHandler webjarsHandler = (ResourceWebHandler) hm.getUrlMap()
@@ -146,7 +146,7 @@ public void shouldMapResourcesToCustomPath() throws Exception {
146146
.isInstanceOf(ResourceWebHandler.class);
147147
ResourceWebHandler staticHandler = (ResourceWebHandler) hm.getUrlMap()
148148
.get("/static/**");
149-
assertThat(staticHandler.getLocations()).hasSize(5);
149+
assertThat(staticHandler.getLocations()).hasSize(4);
150150
}
151151

152152
@Test

spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfigurationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ public void faviconMappingUsesStaticLocations() {
442442
.withPropertyValues("spring.resources.static-locations=classpath:/static")
443443
.run((context) -> assertThat(
444444
getFaviconMappingLocations(context).get("/**/favicon.ico"))
445-
.hasSize(2));
445+
.hasSize(3));
446446
}
447447

448448
@Test

spring-boot-docs/src/main/asciidoc/spring-boot-features.adoc

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,10 +1900,11 @@ can be achieved as follows:
19001900
----
19011901

19021902
You can also customize the static resource locations using
1903-
`spring.resources.static-locations` (replacing the default values with a list of directory
1904-
locations). If you do this the default welcome page detection will switch to your custom
1905-
locations, so if there is an `index.html` in any of your locations on startup, it will be
1906-
the home page of the application.
1903+
`spring.resources.static-locations` (replacing the default values with a list
1904+
of directory locations). The root Servlet context path `"/"` will be automatically
1905+
added as a location as well. If you do this the default welcome page detection will
1906+
switch to your custom locations, so if there is an `index.html` in any of your locations
1907+
on startup, it will be the home page of the application.
19071908

19081909
In addition to the '`standard`' static resource locations above, a special case is made
19091910
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
22492250
non-blocking, and implements the http://www.reactive-streams.org/[Reactive Streams]
22502251
specification through http://projectreactor.io/[the Reactor project].
22512252

2252-
Spring WebFlux comes in two flavors the annotation-based one is quite close to the
2253+
Spring WebFlux comes in two flavors; the annotation-based one is quite close to the
22532254
Spring MVC model we know:
22542255

22552256
[source,java,indent=0]
@@ -3066,7 +3067,7 @@ auto-configured. In this example it's pulled in transitively via
30663067
`spring-boot-starter-data-jpa`.
30673068

30683069
TIP: If, for whatever reason, you do configure the connection URL for an embedded
3069-
database, care should be taken to ensure that the databases automatic shutdown is
3070+
database, care should be taken to ensure that the database's automatic shutdown is
30703071
disabled. If you're using H2 you should use `DB_CLOSE_ON_EXIT=FALSE` to do so. If you're
30713072
using HSQLDB, you should ensure that `shutdown=true` is not used. Disabling the database's
30723073
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
45734574

45744575
[[boot-features-caching-provider-caffeine]]
45754576
==== Caffeine
4576-
Caffeine is a Java 8 rewrite of Guavas cache that supersede the Guava support. If
4577+
Caffeine is a Java 8 rewrite of Guava's cache that supersede the Guava support. If
45774578
Caffeine is present, a `CaffeineCacheManager` (provided by the
45784579
`spring-boot-starter-cache` '`Starter`') is auto-configured. Caches can be created
45794580
on startup using the `spring.cache.cache-names` property and customized by one of the

0 commit comments

Comments
 (0)