From 3c112703d9dc8e75152e658282f0c75b2a6cbeaa Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 25 Jul 2025 22:40:09 +0200 Subject: [PATCH] Introduce useCaches flag on UrlResource (for URLConnection access) Propagated from PathMatchingResourcePatternResolver's setUseCaches. Closes gh-35218 --- .../io/AbstractFileResolvingResource.java | 12 +++- .../core/io/FileUrlResource.java | 4 +- .../springframework/core/io/UrlResource.java | 36 +++++++++- .../PathMatchingResourcePatternResolver.java | 67 +++++++++++++------ 4 files changed, 93 insertions(+), 26 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java index 39b7287a793..2aed7cc6967 100644 --- a/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/AbstractFileResolvingResource.java @@ -361,12 +361,22 @@ public abstract class AbstractFileResolvingResource extends AbstractResource { * @throws IOException if thrown from URLConnection methods */ protected void customizeConnection(URLConnection con) throws IOException { - ResourceUtils.useCachesIfNecessary(con); + useCachesIfNecessary(con); if (con instanceof HttpURLConnection httpCon) { customizeConnection(httpCon); } } + /** + * Apply {@link URLConnection#setUseCaches useCaches} if necessary. + * @param con the URLConnection to customize + * @since 6.2.10 + * @see ResourceUtils#useCachesIfNecessary(URLConnection) + */ + void useCachesIfNecessary(URLConnection con) { + ResourceUtils.useCachesIfNecessary(con); + } + /** * Customize the given {@link HttpURLConnection} before fetching the resource. *

Can be overridden in subclasses for configuring request headers and timeouts. diff --git a/spring-core/src/main/java/org/springframework/core/io/FileUrlResource.java b/spring-core/src/main/java/org/springframework/core/io/FileUrlResource.java index 300b5be4a3f..1878432af63 100644 --- a/spring-core/src/main/java/org/springframework/core/io/FileUrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/FileUrlResource.java @@ -109,7 +109,9 @@ public class FileUrlResource extends UrlResource implements WritableResource { @Override public Resource createRelative(String relativePath) throws MalformedURLException { - return new FileUrlResource(createRelativeURL(relativePath)); + FileUrlResource resource = new FileUrlResource(createRelativeURL(relativePath)); + resource.useCaches = this.useCaches; + return resource; } } diff --git a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java index 80ce1680590..8ca9b80c0dc 100644 --- a/spring-core/src/main/java/org/springframework/core/io/UrlResource.java +++ b/spring-core/src/main/java/org/springframework/core/io/UrlResource.java @@ -67,6 +67,12 @@ public class UrlResource extends AbstractFileResolvingResource { @Nullable private volatile String cleanedUrl; + /** + * Whether to use URLConnection caches ({@code null} means default). + */ + @Nullable + volatile Boolean useCaches; + /** * Create a new {@code UrlResource} based on the given URL object. @@ -216,11 +222,22 @@ public class UrlResource extends AbstractFileResolvingResource { return cleanedUrl; } + /** + * Set an explicit flag for {@link URLConnection#setUseCaches}, + * to be applied for any {@link URLConnection} operation in this resource. + *

By default, caching will be applied only to jar resources. + * An explicit {@code true} flag applies caching to all resources, whereas an + * explicit {@code false} flag turns off caching for jar resources as well. + * @since 6.2.10 + * @see ResourceUtils#useCachesIfNecessary + */ + public void setUseCaches(boolean useCaches) { + this.useCaches = useCaches; + } + /** * This implementation opens an InputStream for the given URL. - *

It sets the {@code useCaches} flag to {@code false}, - * mainly to avoid jar file locking on Windows. * @see java.net.URL#openConnection() * @see java.net.URLConnection#setUseCaches(boolean) * @see java.net.URLConnection#getInputStream() @@ -251,6 +268,17 @@ public class UrlResource extends AbstractFileResolvingResource { } } + @Override + void useCachesIfNecessary(URLConnection con) { + Boolean useCaches = this.useCaches; + if (useCaches != null) { + con.setUseCaches(useCaches); + } + else { + super.useCachesIfNecessary(con); + } + } + /** * This implementation returns the underlying URL reference. */ @@ -305,7 +333,9 @@ public class UrlResource extends AbstractFileResolvingResource { */ @Override public Resource createRelative(String relativePath) throws MalformedURLException { - return new UrlResource(createRelativeURL(relativePath)); + UrlResource resource = new UrlResource(createRelativeURL(relativePath)); + resource.useCaches = this.useCaches; + return resource; } /** diff --git a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java index 1634796d157..0ecabb7e19d 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java @@ -260,7 +260,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol private PathMatcher pathMatcher = new AntPathMatcher(); - private boolean useCaches = true; + @Nullable + private Boolean useCaches; private final Map rootDirCache = new ConcurrentHashMap<>(); @@ -342,10 +343,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol * the {@link JarURLConnection} level as well as within this resolver instance. *

Note that {@link JarURLConnection#setDefaultUseCaches} can be turned off * independently. This resolver-level setting is designed to only enforce - * {@code JarURLConnection#setUseCaches(false)} if necessary but otherwise - * leaves the JVM-level default in place. + * {@code JarURLConnection#setUseCaches(true/false)} if necessary but otherwise + * leaves the JVM-level default in place (if this setter has not been called). + *

As of 6.2.10, this setting propagates to {@link UrlResource#setUseCaches}. * @since 6.1.19 * @see JarURLConnection#setUseCaches + * @see UrlResource#setUseCaches * @see #clearCache() */ public void setUseCaches(boolean useCaches) { @@ -355,7 +358,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol @Override public Resource getResource(String location) { - return getResourceLoader().getResource(location); + Resource resource = getResourceLoader().getResource(location); + if (this.useCaches != null && resource instanceof UrlResource urlResource) { + urlResource.setUseCaches(this.useCaches); + } + return resource; } @Override @@ -473,20 +480,27 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } } else { + UrlResource resource = null; String urlString = url.toString(); String cleanedPath = StringUtils.cleanPath(urlString); if (!cleanedPath.equals(urlString)) { // Prefer cleaned URL, aligned with UrlResource#createRelative(String) try { // Retain original URL instance, potentially including custom URLStreamHandler. - return new UrlResource(new URL(url, cleanedPath)); + resource = new UrlResource(new URL(url, cleanedPath)); } catch (MalformedURLException ex) { // Fallback to regular URL construction below... } } // Retain original URL instance, potentially including custom URLStreamHandler. - return new UrlResource(url); + if (resource == null) { + resource = new UrlResource(url); + } + if (this.useCaches != null) { + resource.setUseCaches(this.useCaches); + } + return resource; } } @@ -505,6 +519,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol UrlResource jarResource = (ResourceUtils.URL_PROTOCOL_JAR.equals(url.getProtocol()) ? new UrlResource(url) : new UrlResource(ResourceUtils.JAR_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR)); + if (this.useCaches != null) { + jarResource.setUseCaches(this.useCaches); + } if (jarResource.exists()) { result.add(jarResource); } @@ -556,7 +573,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol Set entries = this.manifestEntriesCache; if (entries == null) { entries = getClassPathManifestEntries(); - if (this.useCaches) { + if (this.useCaches == null || this.useCaches) { this.manifestEntriesCache = entries; } } @@ -577,7 +594,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol try { File jar = new File(path).getAbsoluteFile(); if (jar.isFile() && seen.add(jar)) { - manifestEntries.add(ClassPathManifestEntry.of(jar)); + manifestEntries.add(ClassPathManifestEntry.of(jar, this.useCaches)); manifestEntries.addAll(getClassPathManifestEntriesFromJar(jar)); } } @@ -616,7 +633,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } File candidate = new File(parent, path); if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) { - manifestEntries.add(ClassPathManifestEntry.of(candidate)); + manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches)); } } } @@ -710,7 +727,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (rootDirResources == null) { // Lookup for specific directory, creating a cache entry for it. rootDirResources = getResources(rootDirPath); - if (this.useCaches) { + if (this.useCaches == null || this.useCaches) { this.rootDirCache.put(rootDirPath, rootDirResources); } } @@ -729,7 +746,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (resolvedUrl != null) { rootDirUrl = resolvedUrl; } - rootDirResource = new UrlResource(rootDirUrl); + UrlResource urlResource = new UrlResource(rootDirUrl); + if (this.useCaches != null) { + urlResource.setUseCaches(this.useCaches); + } + rootDirResource = urlResource; } if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) { result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher())); @@ -865,8 +886,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (con instanceof JarURLConnection jarCon) { // Should usually be the case for traditional JAR files. - if (!this.useCaches) { - jarCon.setUseCaches(false); + if (this.useCaches != null) { + jarCon.setUseCaches(this.useCaches); } try { jarFile = jarCon.getJarFile(); @@ -931,7 +952,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } } } - if (this.useCaches) { + if (this.useCaches == null || this.useCaches) { // Cache jar entries in TreeSet for efficient searching on re-encounter. this.jarEntriesCache.put(jarFileUrl, entriesCache); } @@ -1257,10 +1278,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol private static final String JARFILE_URL_PREFIX = ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX; - static ClassPathManifestEntry of(File file) throws MalformedURLException { + static ClassPathManifestEntry of(File file, @Nullable Boolean useCaches) throws MalformedURLException { String path = fixPath(file.getAbsolutePath()); - Resource resource = asJarFileResource(path); - Resource alternative = createAlternative(path); + Resource resource = asJarFileResource(path, useCaches); + Resource alternative = createAlternative(path, useCaches); return new ClassPathManifestEntry(resource, alternative); } @@ -1281,18 +1302,22 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol * @return the alternative form or {@code null} */ @Nullable - private static Resource createAlternative(String path) { + private static Resource createAlternative(String path, @Nullable Boolean useCaches) { try { String alternativePath = path.startsWith("/") ? path.substring(1) : "/" + path; - return asJarFileResource(alternativePath); + return asJarFileResource(alternativePath, useCaches); } catch (MalformedURLException ex) { return null; } } - private static Resource asJarFileResource(String path) throws MalformedURLException { - return new UrlResource(JARFILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR); + private static Resource asJarFileResource(String path, @Nullable Boolean useCaches) throws MalformedURLException { + UrlResource resource = new UrlResource(JARFILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR); + if (useCaches != null) { + resource.setUseCaches(useCaches); + } + return resource; } }