From 3ddc607b3eaebfa54caa28f94dc794e557eff4e1 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 31 Mar 2025 16:38:28 +0200 Subject: [PATCH 1/3] Add spring.locking.strict property to common appendix See gh-34303 --- framework-docs/modules/ROOT/pages/appendix.adoc | 6 ++++++ .../beans/factory/support/DefaultListableBeanFactory.java | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/framework-docs/modules/ROOT/pages/appendix.adoc b/framework-docs/modules/ROOT/pages/appendix.adoc index 896453d8e9..9a8c9048c0 100644 --- a/framework-docs/modules/ROOT/pages/appendix.adoc +++ b/framework-docs/modules/ROOT/pages/appendix.adoc @@ -92,6 +92,12 @@ the repeated JNDI lookup overhead. See {spring-framework-api}++/jndi/JndiLocatorDelegate.html#IGNORE_JNDI_PROPERTY_NAME++[`JndiLocatorDelegate`] for details. +| `spring.locking.strict` +| Instructs Spring to enforce strict locking during bean creation, rather than the mix of +strict and lenient locking that 6.2 applies by default. See +{spring-framework-api}++/beans/factory/support/DefaultListableBeanFactory.html#STRICT_LOCKING_PROPERTY_NAME++[`DefaultListableBeanFactory`] +for details. + | `spring.objenesis.ignore` | Instructs Spring to ignore Objenesis, not even attempting to use it. See {spring-framework-api}++/objenesis/SpringObjenesis.html#IGNORE_OBJENESIS_PROPERTY_NAME++[`SpringObjenesis`] diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index cf395ec5c8..3ce177368d 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -130,7 +130,7 @@ public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFacto implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable { /** - * System property that instructs Spring to enforce string locking during bean creation, + * System property that instructs Spring to enforce strict locking during bean creation, * rather than the mix of strict and lenient locking that 6.2 applies by default. Setting * this flag to "true" restores 6.1.x style locking in the entire pre-instantiation phase. * @since 6.2.6 From 743f32675d445a18224ec8968277f095376bae76 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 31 Mar 2025 16:39:18 +0200 Subject: [PATCH 2/3] Only attempt load for CGLIB classes in AOT mode Closes gh-34677 --- .../aop/framework/CglibAopProxy.java | 5 +++-- .../CglibSubclassingInstantiationStrategy.java | 5 +++-- .../annotation/ConfigurationClassEnhancer.java | 17 +++++++---------- .../ConfigurationClassEnhancerTests.java | 4 ++-- .../annotation/MvcUriComponentsBuilder.java | 3 ++- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java index aad0b4e9e0..8b28ce9c23 100644 --- a/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java +++ b/spring-aop/src/main/java/org/springframework/aop/framework/CglibAopProxy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,7 @@ import org.springframework.aop.AopInvocationException; import org.springframework.aop.RawTargetAccess; import org.springframework.aop.TargetSource; import org.springframework.aop.support.AopUtils; +import org.springframework.aot.AotDetector; import org.springframework.cglib.core.ClassLoaderAwareGeneratorStrategy; import org.springframework.cglib.core.CodeGenerationException; import org.springframework.cglib.core.GeneratorStrategy; @@ -205,7 +206,7 @@ class CglibAopProxy implements AopProxy, Serializable { enhancer.setSuperclass(proxySuperClass); enhancer.setInterfaces(AopProxyUtils.completeProxiedInterfaces(this.advised)); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setAttemptLoad(true); + enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts()); enhancer.setStrategy(KotlinDetector.isKotlinType(proxySuperClass) ? new ClassLoaderAwareGeneratorStrategy(classLoader) : new ClassLoaderAwareGeneratorStrategy(classLoader, undeclaredThrowableStrategy) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java index 4c1f826f56..17bcf2482a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/CglibSubclassingInstantiationStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.lang.reflect.Method; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aot.AotDetector; import org.springframework.beans.BeanInstantiationException; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.BeanFactory; @@ -153,7 +154,7 @@ public class CglibSubclassingInstantiationStrategy extends SimpleInstantiationSt Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(beanDefinition.getBeanClass()); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setAttemptLoad(true); + enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts()); if (this.owner instanceof ConfigurableBeanFactory cbf) { ClassLoader cl = cbf.getBeanClassLoader(); enhancer.setStrategy(new ClassLoaderAwareGeneratorStrategy(cl)); diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java index 9b50adddef..2c68f10d09 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassEnhancer.java @@ -26,6 +26,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.aop.scope.ScopedProxyFactoryBean; +import org.springframework.aot.AotDetector; import org.springframework.asm.Opcodes; import org.springframework.asm.Type; import org.springframework.beans.factory.BeanDefinitionStoreException; @@ -138,26 +139,22 @@ class ConfigurationClassEnhancer { Enhancer enhancer = new Enhancer(); if (classLoader != null) { enhancer.setClassLoader(classLoader); + if (classLoader instanceof SmartClassLoader smartClassLoader && + smartClassLoader.isClassReloadable(configSuperClass)) { + enhancer.setUseCache(false); + } } enhancer.setSuperclass(configSuperClass); enhancer.setInterfaces(new Class[] {EnhancedConfiguration.class}); enhancer.setUseFactory(false); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setAttemptLoad(!isClassReloadable(configSuperClass, classLoader)); + enhancer.setAttemptLoad(enhancer.getUseCache() && AotDetector.useGeneratedArtifacts()); enhancer.setStrategy(new BeanFactoryAwareGeneratorStrategy(classLoader)); enhancer.setCallbackFilter(CALLBACK_FILTER); enhancer.setCallbackTypes(CALLBACK_FILTER.getCallbackTypes()); return enhancer; } - /** - * Checks whether the given configuration class is reloadable. - */ - private boolean isClassReloadable(Class configSuperClass, @Nullable ClassLoader classLoader) { - return (classLoader instanceof SmartClassLoader smartClassLoader && - smartClassLoader.isClassReloadable(configSuperClass)); - } - /** * Uses enhancer to generate a subclass of superclass, * ensuring that callbacks are registered for the new subclass. @@ -548,7 +545,7 @@ class ConfigurationClassEnhancer { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(factoryBean.getClass()); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setAttemptLoad(true); + enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts()); enhancer.setCallbackType(MethodInterceptor.class); // Ideally create enhanced FactoryBean proxy without constructor side effects, diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java index 052f27f43f..ea73c24e70 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassEnhancerTests.java @@ -76,7 +76,7 @@ class ConfigurationClassEnhancerTests { classLoader = new BasicSmartClassLoader(getClass().getClassLoader()); enhancedClass = configurationClassEnhancer.enhance(MyConfigWithPublicClass.class, classLoader); assertThat(MyConfigWithPublicClass.class).isAssignableFrom(enhancedClass); - assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader); } @Test @@ -126,7 +126,7 @@ class ConfigurationClassEnhancerTests { classLoader = new BasicSmartClassLoader(getClass().getClassLoader()); enhancedClass = configurationClassEnhancer.enhance(MyConfigWithNonPublicMethod.class, classLoader); assertThat(MyConfigWithNonPublicMethod.class).isAssignableFrom(enhancedClass); - assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader.getParent()); + assertThat(enhancedClass.getClassLoader()).isEqualTo(classLoader); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java index cf99f0d9ee..f5a915d054 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/MvcUriComponentsBuilder.java @@ -29,6 +29,7 @@ import jakarta.servlet.http.HttpServletRequest; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.aot.AotDetector; import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.cglib.core.SpringNamingPolicy; @@ -793,7 +794,7 @@ public class MvcUriComponentsBuilder { enhancer.setSuperclass(controllerType); enhancer.setInterfaces(new Class[] {MethodInvocationInfo.class}); enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE); - enhancer.setAttemptLoad(true); + enhancer.setAttemptLoad(AotDetector.useGeneratedArtifacts()); enhancer.setCallbackType(MethodInterceptor.class); Class proxyClass = enhancer.createClass(); From 7b08feeb6dc32890ae0e1374c6fbee0cae84d937 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Mon, 31 Mar 2025 16:41:16 +0200 Subject: [PATCH 3/3] Make jar caching configurable through setUseCaches Closes gh-34678 --- .../PathMatchingResourcePatternResolver.java | 43 ++++++++++++++++--- ...hMatchingResourcePatternResolverTests.java | 3 +- 2 files changed, 38 insertions(+), 8 deletions(-) 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 127d457d9e..7fe7c54b08 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 @@ -215,6 +215,8 @@ import org.springframework.util.StringUtils; */ public class PathMatchingResourcePatternResolver implements ResourcePatternResolver { + private static final Resource[] EMPTY_RESOURCE_ARRAY = {}; + private static final Log logger = LogFactory.getLog(PathMatchingResourcePatternResolver.class); /** @@ -257,6 +259,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol private PathMatcher pathMatcher = new AntPathMatcher(); + private boolean useCaches = true; + private final Map rootDirCache = new ConcurrentHashMap<>(); private final Map> jarEntriesCache = new ConcurrentHashMap<>(); @@ -331,6 +335,22 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol return this.pathMatcher; } + /** + * Specify whether this resolver should use jar caches. Default is {@code true}. + *

Switch this flag to {@code false} in order to avoid any jar caching, at + * 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. + * @since 6.1.19 + * @see JarURLConnection#setUseCaches + * @see #clearCache() + */ + public void setUseCaches(boolean useCaches) { + this.useCaches = useCaches; + } + @Override public Resource getResource(String location) { @@ -354,7 +374,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol // all class path resources with the given name Collections.addAll(resources, findAllClassPathResources(locationPatternWithoutPrefix)); } - return resources.toArray(new Resource[0]); + return resources.toArray(EMPTY_RESOURCE_ARRAY); } else { // Generally only look for a pattern after a prefix here, @@ -398,7 +418,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (logger.isTraceEnabled()) { logger.trace("Resolved class path location [" + path + "] to resources " + result); } - return result.toArray(new Resource[0]); + return result.toArray(EMPTY_RESOURCE_ARRAY); } /** @@ -535,7 +555,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol Set entries = this.manifestEntriesCache; if (entries == null) { entries = getClassPathManifestEntries(); - this.manifestEntriesCache = entries; + if (this.useCaches) { + this.manifestEntriesCache = entries; + } } for (ClassPathManifestEntry entry : entries) { if (!result.contains(entry.resource()) && @@ -687,7 +709,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (rootDirResources == null) { // Lookup for specific directory, creating a cache entry for it. rootDirResources = getResources(rootDirPath); - this.rootDirCache.put(rootDirPath, rootDirResources); + if (this.useCaches) { + this.rootDirCache.put(rootDirPath, rootDirResources); + } } } @@ -719,7 +743,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol if (logger.isTraceEnabled()) { logger.trace("Resolved location pattern [" + locationPattern + "] to resources " + result); } - return result.toArray(new Resource[0]); + return result.toArray(EMPTY_RESOURCE_ARRAY); } /** @@ -840,6 +864,9 @@ 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); + } try { jarFile = jarCon.getJarFile(); jarFileUrl = jarCon.getJarFileURL().toExternalForm(); @@ -903,8 +930,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol } } } - // Cache jar entries in TreeSet for efficient searching on re-encounter. - this.jarEntriesCache.put(jarFileUrl, entriesCache); + if (this.useCaches) { + // Cache jar entries in TreeSet for efficient searching on re-encounter. + this.jarEntriesCache.put(jarFileUrl, entriesCache); + } return result; } finally { diff --git a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java index af1d12e0b4..780fa23316 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/PathMatchingResourcePatternResolverTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -132,6 +132,7 @@ class PathMatchingResourcePatternResolverTests { Path rootDir = Paths.get("src/test/resources/custom%23root").toAbsolutePath(); URL root = new URL("file:" + rootDir + "/"); resolver = new PathMatchingResourcePatternResolver(new DefaultResourceLoader(new URLClassLoader(new URL[] {root}))); + resolver.setUseCaches(false); assertExactFilenames("classpath*:scanned/*.txt", "resource#test1.txt", "resource#test2.txt"); }