Merge branch '6.2.x'

# Conflicts:
#	spring-core/src/main/java/org/springframework/core/io/support/PathMatchingResourcePatternResolver.java
This commit is contained in:
Juergen Hoeller 2025-07-25 22:42:14 +02:00
commit eaccb56de9
7 changed files with 131 additions and 36 deletions

View File

@ -1451,11 +1451,18 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto
} }
} }
@Override
protected void addSingleton(String beanName, Object singletonObject) {
super.addSingleton(beanName, singletonObject);
Predicate<Class<?>> filter = (beanType -> beanType != Object.class && beanType.isInstance(singletonObject));
this.allBeanNamesByType.keySet().removeIf(filter);
this.singletonBeanNamesByType.keySet().removeIf(filter);
}
@Override @Override
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException { public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
super.registerSingleton(beanName, singletonObject); super.registerSingleton(beanName, singletonObject);
updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName)); updateManualSingletonNames(set -> set.add(beanName), set -> !this.beanDefinitionMap.containsKey(beanName));
clearByTypeCache();
} }
@Override @Override

View File

@ -3211,6 +3211,29 @@ class DefaultListableBeanFactoryTests {
assertThat(holder.getNonPublicEnum()).isEqualTo(NonPublicEnum.VALUE_1); assertThat(holder.getNonPublicEnum()).isEqualTo(NonPublicEnum.VALUE_1);
} }
@Test
void mostSpecificCacheEntryForTypeMatching() {
RootBeanDefinition bd1 = new RootBeanDefinition();
bd1.setFactoryBeanName("config");
bd1.setFactoryMethodName("create");
lbf.registerBeanDefinition("config", new RootBeanDefinition(BeanWithFactoryMethod.class));
lbf.registerBeanDefinition("bd1", bd1);
lbf.registerBeanDefinition("bd2", new RootBeanDefinition(NestedTestBean.class));
lbf.freezeConfiguration();
String[] allBeanNames = lbf.getBeanNamesForType(Object.class);
String[] nestedBeanNames = lbf.getBeanNamesForType(NestedTestBean.class);
assertThat(lbf.getType("bd1")).isEqualTo(TestBean.class);
assertThat(lbf.getBeanNamesForType(TestBean.class)).containsExactly("bd1");
assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).isEmpty();
lbf.getBean("bd1");
assertThat(lbf.getType("bd1")).isEqualTo(DerivedTestBean.class);
assertThat(lbf.getBeanNamesForType(TestBean.class)).containsExactly("bd1");
assertThat(lbf.getBeanNamesForType(DerivedTestBean.class)).containsExactly("bd1");
assertThat(lbf.getBeanNamesForType(NestedTestBean.class)).isSameAs(nestedBeanNames);
assertThat(lbf.getBeanNamesForType(Object.class)).isSameAs(allBeanNames);
}
private int registerBeanDefinitions(Properties p) { private int registerBeanDefinitions(Properties p) {
return registerBeanDefinitions(p, null); return registerBeanDefinitions(p, null);
@ -3427,7 +3450,7 @@ class DefaultListableBeanFactoryTests {
} }
public TestBean create() { public TestBean create() {
TestBean tb = new TestBean(); DerivedTestBean tb = new DerivedTestBean();
tb.setName(this.name); tb.setName(this.name);
return tb; return tb;
} }
@ -3655,11 +3678,11 @@ class DefaultListableBeanFactoryTests {
private FactoryBean<?> factoryBean; private FactoryBean<?> factoryBean;
public final FactoryBean<?> getFactoryBean() { public FactoryBean<?> getFactoryBean() {
return this.factoryBean; return this.factoryBean;
} }
public final void setFactoryBean(final FactoryBean<?> factoryBean) { public void setFactoryBean(FactoryBean<?> factoryBean) {
this.factoryBean = factoryBean; this.factoryBean = factoryBean;
} }
} }

View File

@ -361,12 +361,22 @@ public abstract class AbstractFileResolvingResource extends AbstractResource {
* @throws IOException if thrown from URLConnection methods * @throws IOException if thrown from URLConnection methods
*/ */
protected void customizeConnection(URLConnection con) throws IOException { protected void customizeConnection(URLConnection con) throws IOException {
ResourceUtils.useCachesIfNecessary(con); useCachesIfNecessary(con);
if (con instanceof HttpURLConnection httpCon) { if (con instanceof HttpURLConnection httpCon) {
customizeConnection(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. * Customize the given {@link HttpURLConnection} before fetching the resource.
* <p>Can be overridden in subclasses for configuring request headers and timeouts. * <p>Can be overridden in subclasses for configuring request headers and timeouts.

View File

@ -109,7 +109,9 @@ public class FileUrlResource extends UrlResource implements WritableResource {
@Override @Override
public Resource createRelative(String relativePath) throws MalformedURLException { public Resource createRelative(String relativePath) throws MalformedURLException {
return new FileUrlResource(createRelativeURL(relativePath)); FileUrlResource resource = new FileUrlResource(createRelativeURL(relativePath));
resource.useCaches = this.useCaches;
return resource;
} }
} }

View File

@ -66,6 +66,12 @@ public class UrlResource extends AbstractFileResolvingResource {
*/ */
private volatile @Nullable String cleanedUrl; private volatile @Nullable 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. * Create a new {@code UrlResource} based on the given URL object.
@ -215,11 +221,22 @@ public class UrlResource extends AbstractFileResolvingResource {
return cleanedUrl; return cleanedUrl;
} }
/**
* Set an explicit flag for {@link URLConnection#setUseCaches},
* to be applied for any {@link URLConnection} operation in this resource.
* <p>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. * This implementation opens an InputStream for the given URL.
* <p>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.URL#openConnection()
* @see java.net.URLConnection#setUseCaches(boolean) * @see java.net.URLConnection#setUseCaches(boolean)
* @see java.net.URLConnection#getInputStream() * @see java.net.URLConnection#getInputStream()
@ -250,6 +267,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. * This implementation returns the underlying URL reference.
*/ */
@ -304,7 +332,9 @@ public class UrlResource extends AbstractFileResolvingResource {
*/ */
@Override @Override
public Resource createRelative(String relativePath) throws MalformedURLException { public Resource createRelative(String relativePath) throws MalformedURLException {
return new UrlResource(createRelativeURL(relativePath)); UrlResource resource = new UrlResource(createRelativeURL(relativePath));
resource.useCaches = this.useCaches;
return resource;
} }
/** /**

View File

@ -259,7 +259,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private PathMatcher pathMatcher = new AntPathMatcher(); private PathMatcher pathMatcher = new AntPathMatcher();
private boolean useCaches = true; @Nullable
private Boolean useCaches;
private final Map<String, Resource[]> rootDirCache = new ConcurrentHashMap<>(); private final Map<String, Resource[]> rootDirCache = new ConcurrentHashMap<>();
@ -339,10 +340,12 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
* the {@link JarURLConnection} level as well as within this resolver instance. * the {@link JarURLConnection} level as well as within this resolver instance.
* <p>Note that {@link JarURLConnection#setDefaultUseCaches} can be turned off * <p>Note that {@link JarURLConnection#setDefaultUseCaches} can be turned off
* independently. This resolver-level setting is designed to only enforce * independently. This resolver-level setting is designed to only enforce
* {@code JarURLConnection#setUseCaches(false)} if necessary but otherwise * {@code JarURLConnection#setUseCaches(true/false)} if necessary but otherwise
* leaves the JVM-level default in place. * leaves the JVM-level default in place (if this setter has not been called).
* <p>As of 6.2.10, this setting propagates to {@link UrlResource#setUseCaches}.
* @since 6.1.19 * @since 6.1.19
* @see JarURLConnection#setUseCaches * @see JarURLConnection#setUseCaches
* @see UrlResource#setUseCaches
* @see #clearCache() * @see #clearCache()
*/ */
public void setUseCaches(boolean useCaches) { public void setUseCaches(boolean useCaches) {
@ -352,7 +355,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
@Override @Override
public Resource getResource(String location) { 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 @Override
@ -470,20 +477,27 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
} }
} }
else { else {
UrlResource resource = null;
String urlString = url.toString(); String urlString = url.toString();
String cleanedPath = StringUtils.cleanPath(urlString); String cleanedPath = StringUtils.cleanPath(urlString);
if (!cleanedPath.equals(urlString)) { if (!cleanedPath.equals(urlString)) {
// Prefer cleaned URL, aligned with UrlResource#createRelative(String) // Prefer cleaned URL, aligned with UrlResource#createRelative(String)
try { try {
// Retain original URL instance, potentially including custom URLStreamHandler. // 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) { catch (MalformedURLException ex) {
// Fallback to regular URL construction below... // Fallback to regular URL construction below...
} }
} }
// Retain original URL instance, potentially including custom URLStreamHandler. // 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;
} }
} }
@ -502,6 +516,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
UrlResource jarResource = (ResourceUtils.URL_PROTOCOL_JAR.equals(url.getProtocol()) ? UrlResource jarResource = (ResourceUtils.URL_PROTOCOL_JAR.equals(url.getProtocol()) ?
new UrlResource(url) : new UrlResource(url) :
new UrlResource(ResourceUtils.JAR_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR)); new UrlResource(ResourceUtils.JAR_URL_PREFIX + url + ResourceUtils.JAR_URL_SEPARATOR));
if (this.useCaches != null) {
jarResource.setUseCaches(this.useCaches);
}
if (jarResource.exists()) { if (jarResource.exists()) {
result.add(jarResource); result.add(jarResource);
} }
@ -553,7 +570,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
Set<ClassPathManifestEntry> entries = this.manifestEntriesCache; Set<ClassPathManifestEntry> entries = this.manifestEntriesCache;
if (entries == null) { if (entries == null) {
entries = getClassPathManifestEntries(); entries = getClassPathManifestEntries();
if (this.useCaches) { if (this.useCaches == null || this.useCaches) {
this.manifestEntriesCache = entries; this.manifestEntriesCache = entries;
} }
} }
@ -574,7 +591,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
try { try {
File jar = new File(path).getAbsoluteFile(); File jar = new File(path).getAbsoluteFile();
if (jar.isFile() && seen.add(jar)) { if (jar.isFile() && seen.add(jar)) {
manifestEntries.add(ClassPathManifestEntry.of(jar)); manifestEntries.add(ClassPathManifestEntry.of(jar, this.useCaches));
manifestEntries.addAll(getClassPathManifestEntriesFromJar(jar)); manifestEntries.addAll(getClassPathManifestEntriesFromJar(jar));
} }
} }
@ -613,7 +630,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
} }
File candidate = new File(parent, path); File candidate = new File(parent, path);
if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) { if (candidate.isFile() && candidate.getCanonicalPath().contains(parent.getCanonicalPath())) {
manifestEntries.add(ClassPathManifestEntry.of(candidate)); manifestEntries.add(ClassPathManifestEntry.of(candidate, this.useCaches));
} }
} }
} }
@ -707,7 +724,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (rootDirResources == null) { if (rootDirResources == null) {
// Lookup for specific directory, creating a cache entry for it. // Lookup for specific directory, creating a cache entry for it.
rootDirResources = getResources(rootDirPath); rootDirResources = getResources(rootDirPath);
if (this.useCaches) { if (this.useCaches == null || this.useCaches) {
this.rootDirCache.put(rootDirPath, rootDirResources); this.rootDirCache.put(rootDirPath, rootDirResources);
} }
} }
@ -726,7 +743,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (resolvedUrl != null) { if (resolvedUrl != null) {
rootDirUrl = resolvedUrl; 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)) { if (rootDirUrl.getProtocol().startsWith(ResourceUtils.URL_PROTOCOL_VFS)) {
result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher())); result.addAll(VfsResourceMatchingDelegate.findMatchingResources(rootDirUrl, subPattern, getPathMatcher()));
@ -862,8 +883,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (con instanceof JarURLConnection jarCon) { if (con instanceof JarURLConnection jarCon) {
// Should usually be the case for traditional JAR files. // Should usually be the case for traditional JAR files.
if (!this.useCaches) { if (this.useCaches != null) {
jarCon.setUseCaches(false); jarCon.setUseCaches(this.useCaches);
} }
try { try {
jarFile = jarCon.getJarFile(); jarFile = jarCon.getJarFile();
@ -928,7 +949,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. // Cache jar entries in TreeSet for efficient searching on re-encounter.
this.jarEntriesCache.put(jarFileUrl, entriesCache); this.jarEntriesCache.put(jarFileUrl, entriesCache);
} }
@ -1251,10 +1272,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private static final String JARFILE_URL_PREFIX = ResourceUtils.JAR_URL_PREFIX + ResourceUtils.FILE_URL_PREFIX; 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()); String path = fixPath(file.getAbsolutePath());
Resource resource = asJarFileResource(path); Resource resource = asJarFileResource(path, useCaches);
Resource alternative = createAlternative(path); Resource alternative = createAlternative(path, useCaches);
return new ClassPathManifestEntry(resource, alternative); return new ClassPathManifestEntry(resource, alternative);
} }
@ -1274,18 +1295,22 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
* @param path the file path (with or without a leading slash) * @param path the file path (with or without a leading slash)
* @return the alternative form or {@code null} * @return the alternative form or {@code null}
*/ */
private static @Nullable Resource createAlternative(String path) { private static @Nullable Resource createAlternative(String path, @Nullable Boolean useCaches) {
try { try {
String alternativePath = path.startsWith("/") ? path.substring(1) : "/" + path; String alternativePath = path.startsWith("/") ? path.substring(1) : "/" + path;
return asJarFileResource(alternativePath); return asJarFileResource(alternativePath, useCaches);
} }
catch (MalformedURLException ex) { catch (MalformedURLException ex) {
return null; return null;
} }
} }
private static Resource asJarFileResource(String path) throws MalformedURLException { private static Resource asJarFileResource(String path, @Nullable Boolean useCaches) throws MalformedURLException {
return new UrlResource(JARFILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR); UrlResource resource = new UrlResource(JARFILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR);
if (useCaches != null) {
resource.setUseCaches(useCaches);
}
return resource;
} }
} }

View File

@ -184,21 +184,19 @@ class PathResourceResolverTests {
private String relativePath; private String relativePath;
public TestUrlResource(String path) throws MalformedURLException { public TestUrlResource(String path) throws MalformedURLException {
super(path); super(path);
} }
public String getSavedRelativePath() {
return this.relativePath;
}
@Override @Override
public Resource createRelative(String relativePath) { public Resource createRelative(String relativePath) {
this.relativePath = relativePath; this.relativePath = relativePath;
return this; return this;
} }
public String getSavedRelativePath() {
return this.relativePath;
}
} }
} }