7272 * <p>This request handler may also be configured with a
7373 * {@link #setResourceResolvers(List) resourcesResolver} and
7474 * {@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
7777 * "locations". An application can configure additional resolvers and
7878 * transformers such as the {@link VersionResourceResolver} which can resolve
7979 * and prepare URLs for resources with a version in the URL.
8585 *
8686 * @author Rossen Stoyanchev
8787 * @author Brian Clozel
88+ * @author Juergen Hoeller
8889 * @since 5.0
8990 */
9091public class ResourceWebHandler implements WebHandler , InitializingBean {
@@ -94,6 +95,9 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
9495 private static final Log logger = LogFactory .getLog (ResourceWebHandler .class );
9596
9697
98+ @ Nullable
99+ private ResourceLoader resourceLoader ;
100+
97101 private final List <String > locationValues = new ArrayList <>(4 );
98102
99103 private final List <Resource > locationResources = new ArrayList <>(4 );
@@ -119,11 +123,18 @@ public class ResourceWebHandler implements WebHandler, InitializingBean {
119123 @ Nullable
120124 private Map <String , MediaType > mediaTypes ;
121125
122- @ Nullable
123- private ResourceLoader resourceLoader ;
124-
125126 private boolean useLastModified = true ;
126127
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+ }
127138
128139 /**
129140 * Accepts a list of String-based location values to be resolved into
@@ -161,9 +172,9 @@ public void setLocations(@Nullable List<Resource> locations) {
161172 * <p>Note that if {@link #setLocationValues(List) locationValues} are provided,
162173 * instead of loaded Resource-based locations, this method will return
163174 * 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} .
167178 * @see #setLocationValues
168179 * @see #setLocations
169180 */
@@ -212,6 +223,22 @@ public List<ResourceTransformer> getResourceTransformers() {
212223 return this .resourceTransformers ;
213224 }
214225
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+
215242 /**
216243 * Set the {@link org.springframework.http.CacheControl} instance to build
217244 * the Cache-Control HTTP response header.
@@ -230,19 +257,48 @@ public CacheControl getCacheControl() {
230257 }
231258
232259 /**
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
235266 */
236- public void setResourceHttpMessageWriter ( @ Nullable ResourceHttpMessageWriter httpMessageWriter ) {
237- this .resourceHttpMessageWriter = httpMessageWriter ;
267+ public void setUseLastModified ( boolean useLastModified ) {
268+ this .useLastModified = useLastModified ;
238269 }
239270
240271 /**
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
242275 */
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 ;
246302 }
247303
248304 /**
@@ -269,36 +325,6 @@ public Map<String, MediaType> getMediaTypes() {
269325 return (this .mediaTypes != null ? this .mediaTypes : Collections .emptyMap ());
270326 }
271327
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- }
302328
303329 @ Override
304330 public void afterPropertiesSet () throws Exception {
@@ -332,7 +358,9 @@ private void resolveResourceLocations() {
332358 }
333359 }
334360
335- result = result .stream ().filter (Resource ::exists ).collect (Collectors .toList ());
361+ if (isOptimizeLocations ()) {
362+ result = result .stream ().filter (Resource ::exists ).collect (Collectors .toList ());
363+ }
336364
337365 this .locationsToUse .clear ();
338366 this .locationsToUse .addAll (result );
0 commit comments