72
72
* <p>This request handler may also be configured with a
73
73
* {@link #setResourceResolvers(List) resourcesResolver} and
74
74
* {@link #setResourceTransformers(List) resourceTransformer} chains to support
75
- * arbitrary resolution and transformation of resources being served. By default a
76
- * {@link PathResourceResolver} simply finds resources based on the configured
75
+ * arbitrary resolution and transformation of resources being served. By default
76
+ * a {@link PathResourceResolver} simply finds resources based on the configured
77
77
* "locations". An application can configure additional resolvers and
78
78
* transformers such as the {@link VersionResourceResolver} which can resolve
79
79
* and prepare URLs for resources with a version in the URL.
85
85
*
86
86
* @author Rossen Stoyanchev
87
87
* @author Brian Clozel
88
+ * @author Juergen Hoeller
88
89
* @since 5.0
89
90
*/
90
91
public class ResourceWebHandler implements WebHandler , InitializingBean {
@@ -94,6 +95,9 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
94
95
private static final Log logger = LogFactory .getLog (ResourceWebHandler .class );
95
96
96
97
98
+ @ Nullable
99
+ private ResourceLoader resourceLoader ;
100
+
97
101
private final List <String > locationValues = new ArrayList <>(4 );
98
102
99
103
private final List <Resource > locationResources = new ArrayList <>(4 );
@@ -119,11 +123,18 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
119
123
@ Nullable
120
124
private Map <String , MediaType > mediaTypes ;
121
125
122
- @ Nullable
123
- private ResourceLoader resourceLoader ;
124
-
125
126
private boolean useLastModified = true ;
126
127
128
+ private boolean optimizeLocations = false ;
129
+
130
+
131
+ /**
132
+ * Provide the ResourceLoader to load {@link #setLocationValues location values} with.
133
+ * @since 5.1
134
+ */
135
+ public void setResourceLoader (ResourceLoader resourceLoader ) {
136
+ this .resourceLoader = resourceLoader ;
137
+ }
127
138
128
139
/**
129
140
* Accepts a list of String-based location values to be resolved into
@@ -161,9 +172,9 @@ public void setLocations(@Nullable List<Resource> locations) {
161
172
* <p>Note that if {@link #setLocationValues(List) locationValues} are provided,
162
173
* instead of loaded Resource-based locations, this method will return
163
174
* empty until after initialization via {@link #afterPropertiesSet()}.
164
- * <p><strong>Note:</strong> As of 5.3.11 the list of locations is filtered
165
- * to exclude those that don't actually exist and therefore the list returned
166
- * from this method may be a subset of all given locations.
175
+ * <p><strong>Note:</strong> As of 5.3.11 the list of locations may be filtered to
176
+ * exclude those that don't actually exist and therefore the list returned from this
177
+ * method may be a subset of all given locations. See {@link #setOptimizeLocations} .
167
178
* @see #setLocationValues
168
179
* @see #setLocations
169
180
*/
@@ -212,6 +223,22 @@ public List<ResourceTransformer> getResourceTransformers() {
212
223
return this .resourceTransformers ;
213
224
}
214
225
226
+ /**
227
+ * Configure the {@link ResourceHttpMessageWriter} to use.
228
+ * <p>By default a {@link ResourceHttpMessageWriter} will be configured.
229
+ */
230
+ public void setResourceHttpMessageWriter (@ Nullable ResourceHttpMessageWriter httpMessageWriter ) {
231
+ this .resourceHttpMessageWriter = httpMessageWriter ;
232
+ }
233
+
234
+ /**
235
+ * Return the configured resource message writer.
236
+ */
237
+ @ Nullable
238
+ public ResourceHttpMessageWriter getResourceHttpMessageWriter () {
239
+ return this .resourceHttpMessageWriter ;
240
+ }
241
+
215
242
/**
216
243
* Set the {@link org.springframework.http.CacheControl} instance to build
217
244
* the Cache-Control HTTP response header.
@@ -230,19 +257,48 @@ public CacheControl getCacheControl() {
230
257
}
231
258
232
259
/**
233
- * Configure the {@link ResourceHttpMessageWriter} to use.
234
- * <p>By default a {@link ResourceHttpMessageWriter} will be configured.
260
+ * Set whether we should look at the {@link Resource#lastModified()}
261
+ * when serving resources and use this information to drive {@code "Last-Modified"}
262
+ * HTTP response headers.
263
+ * <p>This option is enabled by default and should be turned off if the metadata of
264
+ * the static files should be ignored.
265
+ * @since 5.3
235
266
*/
236
- public void setResourceHttpMessageWriter ( @ Nullable ResourceHttpMessageWriter httpMessageWriter ) {
237
- this .resourceHttpMessageWriter = httpMessageWriter ;
267
+ public void setUseLastModified ( boolean useLastModified ) {
268
+ this .useLastModified = useLastModified ;
238
269
}
239
270
240
271
/**
241
- * Return the configured resource message writer.
272
+ * Return whether the {@link Resource#lastModified()} information is used
273
+ * to drive HTTP responses when serving static resources.
274
+ * @since 5.3
242
275
*/
243
- @ Nullable
244
- public ResourceHttpMessageWriter getResourceHttpMessageWriter () {
245
- return this .resourceHttpMessageWriter ;
276
+ public boolean isUseLastModified () {
277
+ return this .useLastModified ;
278
+ }
279
+
280
+ /**
281
+ * Set whether to optimize the specified locations through an existence
282
+ * check on startup, filtering non-existing directories upfront so that
283
+ * they do not have to be checked on every resource access.
284
+ * <p>The default is {@code false}, for defensiveness against zip files
285
+ * without directory entries which are unable to expose the existence of
286
+ * a directory upfront. Switch this flag to {@code true} for optimized
287
+ * access in case of a consistent jar layout with directory entries.
288
+ * @since 5.3.13
289
+ */
290
+ public void setOptimizeLocations (boolean optimizeLocations ) {
291
+ this .optimizeLocations = optimizeLocations ;
292
+ }
293
+
294
+ /**
295
+ * Return whether to optimize the specified locations through an existence
296
+ * check on startup, filtering non-existing directories upfront so that
297
+ * they do not have to be checked on every resource access.
298
+ * @since 5.3.13
299
+ */
300
+ public boolean isOptimizeLocations () {
301
+ return this .optimizeLocations ;
246
302
}
247
303
248
304
/**
@@ -269,36 +325,6 @@ public Map<String, MediaType> getMediaTypes() {
269
325
return (this .mediaTypes != null ? this .mediaTypes : Collections .emptyMap ());
270
326
}
271
327
272
- /**
273
- * Provide the ResourceLoader to load {@link #setLocationValues(List)
274
- * location values} with.
275
- * @since 5.1
276
- */
277
- public void setResourceLoader (ResourceLoader resourceLoader ) {
278
- this .resourceLoader = resourceLoader ;
279
- }
280
-
281
- /**
282
- * Return whether the {@link Resource#lastModified()} information is used
283
- * to drive HTTP responses when serving static resources.
284
- * @since 5.3
285
- */
286
- public boolean isUseLastModified () {
287
- return this .useLastModified ;
288
- }
289
-
290
- /**
291
- * Set whether we should look at the {@link Resource#lastModified()}
292
- * when serving resources and use this information to drive {@code "Last-Modified"}
293
- * HTTP response headers.
294
- * <p>This option is enabled by default and should be turned off if the metadata of
295
- * the static files should be ignored.
296
- * @param useLastModified whether to use the resource last-modified information.
297
- * @since 5.3
298
- */
299
- public void setUseLastModified (boolean useLastModified ) {
300
- this .useLastModified = useLastModified ;
301
- }
302
328
303
329
@ Override
304
330
public void afterPropertiesSet () throws Exception {
@@ -332,7 +358,9 @@ private void resolveResourceLocations() {
332
358
}
333
359
}
334
360
335
- result = result .stream ().filter (Resource ::exists ).collect (Collectors .toList ());
361
+ if (isOptimizeLocations ()) {
362
+ result = result .stream ().filter (Resource ::exists ).collect (Collectors .toList ());
363
+ }
336
364
337
365
this .locationsToUse .clear ();
338
366
this .locationsToUse .addAll (result );
0 commit comments