diff --git a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java index be78c0a4bf5..7cf9836e62e 100644 --- a/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java +++ b/spring-core/src/main/java/org/springframework/core/io/support/SpringFactoriesLoader.java @@ -114,43 +114,17 @@ public class SpringFactoriesLoader { private final Map> factories; - private SpringFactoriesLoader(@Nullable ClassLoader classLoader, String resourceLocation) { - this.classLoader = classLoader; - this.factories = loadFactoriesResource((classLoader != null) ? classLoader - : SpringFactoriesLoader.class.getClassLoader(), resourceLocation); - } - + /** + * Create a new {@link SpringFactoriesLoader} instance. + * @param classLoader the classloader used to instantiate the factories + * @param factories a map of factory class name to implementation class names + */ protected SpringFactoriesLoader(@Nullable ClassLoader classLoader, Map> factories) { this.classLoader = classLoader; this.factories = factories; } - private Map> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) { - Map> result = new LinkedHashMap<>(); - try { - Enumeration urls = classLoader.getResources(resourceLocation); - while (urls.hasMoreElements()) { - UrlResource resource = new UrlResource(urls.nextElement()); - Properties properties = PropertiesLoaderUtils.loadProperties(resource); - properties.forEach((name, value) -> { - List implementations = result.computeIfAbsent(((String) name).trim(), key -> new ArrayList<>()); - Arrays.stream(StringUtils.commaDelimitedListToStringArray((String) value)) - .map(String::trim).forEach(implementations::add); - }); - } - result.replaceAll(this::toDistinctUnmodifiableList); - } - catch (IOException ex) { - throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex); - } - return Collections.unmodifiableMap(result); - } - - private List toDistinctUnmodifiableList(String factoryType, List implementations) { - return implementations.stream().distinct().toList(); - } - /** * Load and instantiate the factory implementations of the given type from * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader and @@ -343,19 +317,47 @@ public class SpringFactoriesLoader { */ public static SpringFactoriesLoader forResourceLocation(@Nullable ClassLoader classLoader, String resourceLocation) { Assert.hasText(resourceLocation, "'resourceLocation' must not be empty"); - Map loaders = SpringFactoriesLoader.cache.get(classLoader); + ClassLoader resourceClassLoader = (classLoader != null) ? classLoader + : SpringFactoriesLoader.class.getClassLoader(); + Map loaders = SpringFactoriesLoader.cache.get(resourceClassLoader); if (loaders == null) { loaders = new ConcurrentReferenceHashMap<>(); - SpringFactoriesLoader.cache.put(classLoader, loaders); + SpringFactoriesLoader.cache.put(resourceClassLoader, loaders); } SpringFactoriesLoader loader = loaders.get(resourceLocation); if (loader == null) { - loader = new SpringFactoriesLoader(classLoader, resourceLocation); + Map> factories = loadFactoriesResource(resourceClassLoader, resourceLocation); + loader = new SpringFactoriesLoader(classLoader, factories); loaders.put(resourceLocation, loader); } return loader; } + private static Map> loadFactoriesResource(ClassLoader classLoader, String resourceLocation) { + Map> result = new LinkedHashMap<>(); + try { + Enumeration urls = classLoader.getResources(resourceLocation); + while (urls.hasMoreElements()) { + UrlResource resource = new UrlResource(urls.nextElement()); + Properties properties = PropertiesLoaderUtils.loadProperties(resource); + properties.forEach((name, value) -> { + List implementations = result.computeIfAbsent(((String) name).trim(), key -> new ArrayList<>()); + Arrays.stream(StringUtils.commaDelimitedListToStringArray((String) value)) + .map(String::trim).forEach(implementations::add); + }); + } + result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList); + } + catch (IOException ex) { + throw new IllegalArgumentException("Unable to load factories from location [" + resourceLocation + "]", ex); + } + return Collections.unmodifiableMap(result); + } + + private static List toDistinctUnmodifiableList(String factoryType, List implementations) { + return implementations.stream().distinct().toList(); + } + /** * Internal instantiator used to create the factory instance. diff --git a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java index 20640400d8f..b9bf4c451bc 100644 --- a/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java +++ b/spring-core/src/test/java/org/springframework/core/io/support/SpringFactoriesLoaderTests.java @@ -34,6 +34,7 @@ import org.springframework.core.io.support.SpringFactoriesLoader.ArgumentResolve import org.springframework.core.io.support.SpringFactoriesLoader.FactoryInstantiator; import org.springframework.core.io.support.SpringFactoriesLoader.FailureHandler; import org.springframework.core.log.LogMessage; +import org.springframework.util.ClassUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; @@ -169,6 +170,13 @@ class SpringFactoriesLoaderTests { assertThat(factories.get(0)).isInstanceOf(MyDummyFactory1.class); } + @Test + void sameCachedResultIsUsedForDefaultClassLoaderAndNullClassLoader() { + SpringFactoriesLoader forNull = SpringFactoriesLoader.forDefaultResourceLocation(null); + SpringFactoriesLoader forDefault = SpringFactoriesLoader.forDefaultResourceLocation(ClassUtils.getDefaultClassLoader()); + assertThat(forNull).isSameAs(forDefault); + } + @Nested class FailureHandlerTests {