From 58c441f9610b14569907059d5d0ca64975fe7653 Mon Sep 17 00:00:00 2001 From: Phillip Webb Date: Wed, 13 Apr 2022 16:39:18 -0700 Subject: [PATCH] Support multiple SpringFactoriesLoader files Update `SpringFactoriesLoader` so that it can load files from arbitrary locations. An instance of the loader class itself is now returned from static factory methods that accept different locations. The recent `ArgumentResolver` and `FailureHandler` `loadFactories` variants are now no longer available as static methods. They are still available as instance methods. The `loadFactories` static method remains to provide back-compatibility with Spring Framework 5.x See gh-28416 --- .../BeanDefinitionsContribution.java | 6 +- .../io/support/SpringFactoriesLoader.java | 240 ++++++++++++------ .../support/SpringFactoriesLoaderTests.java | 62 +++-- .../META-INF/custom/custom-spring.factories | 2 + .../test.factories | 2 + 5 files changed, 207 insertions(+), 105 deletions(-) create mode 100644 spring-core/src/test/resources/META-INF/custom/custom-spring.factories create mode 100644 spring-core/src/test/resources/META-INF/spring/org.springframework.core.io.support.SpringFactoriesLoaderTests/test.factories diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanDefinitionsContribution.java b/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanDefinitionsContribution.java index fa4cd09795..3c3632b08a 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanDefinitionsContribution.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/generator/BeanDefinitionsContribution.java @@ -62,8 +62,10 @@ public class BeanDefinitionsContribution implements BeanFactoryContribution { } private static List initializeProviders(DefaultListableBeanFactory beanFactory) { - List providers = new ArrayList<>(SpringFactoriesLoader.loadFactories( - BeanRegistrationContributionProvider.class, beanFactory.getBeanClassLoader(), ArgumentResolver.from(type -> type.isInstance(beanFactory) ? beanFactory : null))); + List providers = new ArrayList<>( + SpringFactoriesLoader.forDefaultResourceLocation(beanFactory.getBeanClassLoader()).load( + BeanRegistrationContributionProvider.class, + ArgumentResolver.from(type -> type.isInstance(beanFactory) ? beanFactory : null))); providers.add(new DefaultBeanRegistrationContributionProvider(beanFactory)); return providers; } 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 73b87db505..be78c0a4bf 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 @@ -25,7 +25,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; @@ -89,7 +89,7 @@ import org.springframework.util.StringUtils; * @author Phillip Webb * @since 3.2 */ -public final class SpringFactoriesLoader { +public class SpringFactoriesLoader { /** * The location to look for factories. @@ -97,17 +97,59 @@ public final class SpringFactoriesLoader { */ public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; + private static final ArgumentResolver NO_ARGUMENT_RESOLVER = null; + + private static final FailureHandler NO_FAILURE_HANDLER = null; + + private static final FailureHandler THROWING_FAILURE_HANDLER = FailureHandler.throwing(); private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class); - private static final FailureHandler THROWING_HANDLER = FailureHandler.throwing(); - - static final Map>> cache = new ConcurrentReferenceHashMap<>(); + static final Map> cache = new ConcurrentReferenceHashMap<>(); - private SpringFactoriesLoader() { + @Nullable + private final ClassLoader classLoader; + + private final Map> factories; + + + private SpringFactoriesLoader(@Nullable ClassLoader classLoader, String resourceLocation) { + this.classLoader = classLoader; + this.factories = loadFactoriesResource((classLoader != null) ? classLoader + : SpringFactoriesLoader.class.getClassLoader(), resourceLocation); } + 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 @@ -121,12 +163,11 @@ public final class SpringFactoriesLoader { * discovered for a given factory type, only one instance of the duplicated * implementation type will be instantiated. * @param factoryType the interface or abstract class representing the factory - * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @throws IllegalArgumentException if any factory implementation class cannot * be loaded or if an error occurs while instantiating any factory */ - public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader) { - return loadFactories(factoryType, classLoader, null, null); + public List load(Class factoryType) { + return load(factoryType, NO_ARGUMENT_RESOLVER, NO_FAILURE_HANDLER); } /** @@ -138,16 +179,13 @@ public final class SpringFactoriesLoader { * discovered for a given factory type, only one instance of the duplicated * implementation type will be instantiated. * @param factoryType the interface or abstract class representing the factory - * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @param argumentResolver strategy used to resolve constructor arguments by their type * @throws IllegalArgumentException if any factory implementation class cannot * be loaded or if an error occurs while instantiating any factory * @since 6.0 */ - public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader, - @Nullable ArgumentResolver argumentResolver) { - - return loadFactories(factoryType, classLoader, argumentResolver, null); + public List load(Class factoryType, @Nullable ArgumentResolver argumentResolver) { + return load(factoryType, argumentResolver, NO_FAILURE_HANDLER); } /** @@ -161,14 +199,11 @@ public final class SpringFactoriesLoader { *

For any factory implementation class that cannot be loaded or error that occurs while * instantiating it, the given failure handler is called. * @param factoryType the interface or abstract class representing the factory - * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @param failureHandler strategy used to handle factory instantiation failures * @since 6.0 */ - public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader, - @Nullable FailureHandler failureHandler) { - - return loadFactories(factoryType, classLoader, null, failureHandler); + public List load(Class factoryType, @Nullable FailureHandler failureHandler) { + return load(factoryType, NO_ARGUMENT_RESOLVER, failureHandler); } /** @@ -183,24 +218,18 @@ public final class SpringFactoriesLoader { *

For any factory implementation class that cannot be loaded or error that occurs while * instantiating it, the given failure handler is called. * @param factoryType the interface or abstract class representing the factory - * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) * @param argumentResolver strategy used to resolve constructor arguments by their type * @param failureHandler strategy used to handle factory instantiation failures * @since 6.0 */ - public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader, - @Nullable ArgumentResolver argumentResolver, @Nullable FailureHandler failureHandler) { - + public List load(Class factoryType, @Nullable ArgumentResolver argumentResolver, @Nullable FailureHandler failureHandler) { Assert.notNull(factoryType, "'factoryType' must not be null"); - ClassLoader classLoaderToUse = (classLoader != null ? classLoader - : SpringFactoriesLoader.class.getClassLoader()); - List factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse); - logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), factoryImplementationNames)); - List result = new ArrayList<>(factoryImplementationNames.size()); - FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_HANDLER; - for (String factoryImplementationName : factoryImplementationNames) { - T factory = instantiateFactory(factoryImplementationName, factoryType, - classLoaderToUse, argumentResolver, failureHandlerToUse); + List implementationNames = loadFactoryNames(factoryType); + logger.trace(LogMessage.format("Loaded [%s] names: %s", factoryType.getName(), implementationNames)); + List result = new ArrayList<>(implementationNames.size()); + FailureHandler failureHandlerToUse = (failureHandler != null) ? failureHandler : THROWING_FAILURE_HANDLER; + for (String implementationName : implementationNames) { + T factory = instantiateFactory(implementationName, factoryType, argumentResolver, failureHandlerToUse); if (factory != null) { result.add(factory); } @@ -209,6 +238,44 @@ public final class SpringFactoriesLoader { return result; } + private List loadFactoryNames(Class factoryType) { + return this.factories.getOrDefault(factoryType.getName(), Collections.emptyList()); + } + + @Nullable + protected T instantiateFactory(String implementationName, Class type, @Nullable ArgumentResolver argumentResolver, FailureHandler failureHandler) { + try { + Class factoryImplementationClass = ClassUtils.forName(implementationName, this.classLoader); + Assert.isTrue(type.isAssignableFrom(factoryImplementationClass), + () -> "Class [" + implementationName + "] is not assignable to factory type [" + type.getName() + "]"); + FactoryInstantiator factoryInstantiator = FactoryInstantiator.forClass(factoryImplementationClass); + return factoryInstantiator.instantiate(argumentResolver); + } + catch (Throwable ex) { + failureHandler.handleFailure(type, implementationName, ex); + return null; + } + } + + /** + * Load and instantiate the factory implementations of the given type from + * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader. + *

The returned factories are sorted through {@link AnnotationAwareOrderComparator}. + *

As of Spring Framework 5.3, if duplicate implementation class names are + * discovered for a given factory type, only one instance of the duplicated + * implementation type will be instantiated. + *

For more advanced factory loading with {@link ArgumentResolver} or + * {@link FailureHandler} support use {@link #forDefaultResourceLocation(ClassLoader)} + * to obtain a {@link SpringFactoriesLoader} instance. + * @param factoryType the interface or abstract class representing the factory + * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default) + * @throws IllegalArgumentException if any factory implementation class cannot + * be loaded or if an error occurs while instantiating any factory + */ + public static List loadFactories(Class factoryType, @Nullable ClassLoader classLoader) { + return forDefaultResourceLocation(classLoader).load(factoryType); + } + /** * Load the fully qualified class names of factory implementations of the * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given @@ -223,68 +290,70 @@ public final class SpringFactoriesLoader { * @see #loadFactories */ public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) { - ClassLoader classLoaderToUse = (classLoader != null ? classLoader - : SpringFactoriesLoader.class.getClassLoader()); - String factoryTypeName = factoryType.getName(); - return getAllFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList()); + return forDefaultResourceLocation(classLoader).loadFactoryNames(factoryType); } - private static Map> getAllFactories(ClassLoader classLoader) { - Map> result = cache.get(classLoader); - if (result != null) { - return result; - } - result = loadAllFactories(classLoader); - cache.put(classLoader, result); - return result; + /** + * Return a {@link SpringFactoriesLoader} instance that will load and + * instantiate the factory implementations from + * {@value #FACTORIES_RESOURCE_LOCATION}, using the default class loader. + * @return a {@link SpringFactoriesLoader} instance + * @since 6.0 + * @see #forDefaultResourceLocation(ClassLoader) + */ + public static SpringFactoriesLoader forDefaultResourceLocation() { + return forDefaultResourceLocation(null); } - private static Map> loadAllFactories(ClassLoader classLoader) { - Map> result; - result = new HashMap<>(); - try { - Enumeration urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION); - while (urls.hasMoreElements()) { - UrlResource resource = new UrlResource(urls.nextElement()); - Properties properties = PropertiesLoaderUtils.loadProperties(resource); - for (Map.Entry entry : properties.entrySet()) { - String factoryTypeName = ((String) entry.getKey()).trim(); - String[] factoryImplementationNames = - StringUtils.commaDelimitedListToStringArray((String) entry.getValue()); - for (String factoryImplementationName : factoryImplementationNames) { - result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>()) - .add(factoryImplementationName.trim()); - } - } - } - result.replaceAll(SpringFactoriesLoader::toDistinctUnmodifiableList); - } - catch (IOException ex) { - throw new IllegalArgumentException("Unable to load factories from location [" + - FACTORIES_RESOURCE_LOCATION + "]", ex); - } - return Collections.unmodifiableMap(result); + /** + * Return a {@link SpringFactoriesLoader} instance that will load and + * instantiate the factory implementations from + * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader. + * @param classLoader the ClassLoader to use for loading resources; can be + * {@code null} to use the default + * @return a {@link SpringFactoriesLoader} instance + * @since 6.0 + * @see #forDefaultResourceLocation() + */ + public static SpringFactoriesLoader forDefaultResourceLocation(@Nullable ClassLoader classLoader) { + return forResourceLocation(classLoader, FACTORIES_RESOURCE_LOCATION); } - private static List toDistinctUnmodifiableList(String factoryType, List implementations) { - return implementations.stream().distinct().toList(); + /** + * Return a {@link SpringFactoriesLoader} instance that will load and + * instantiate the factory implementations from the given location, using + * the default class loader. + * @return a {@link SpringFactoriesLoader} instance + * @since 6.0 + * @see #forResourceLocation(ClassLoader, String) + */ + public static SpringFactoriesLoader forResourceLocation(String resourceLocation) { + return forResourceLocation(null, resourceLocation); } - @Nullable - private static T instantiateFactory(String factoryImplementationName, - Class factoryType, ClassLoader classLoader, @Nullable ArgumentResolver argumentResolver, - FailureHandler failureHandler) { - try { - Class factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader); - Assert.isTrue(factoryType.isAssignableFrom(factoryImplementationClass), - () -> "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]"); - FactoryInstantiator factoryInstantiator = FactoryInstantiator.forClass(factoryImplementationClass); - return factoryInstantiator.instantiate(argumentResolver); + /** + * Return a {@link SpringFactoriesLoader} instance that will load and + * instantiate the factory implementations from the given location, using + * the given class loader. + * @param classLoader the ClassLoader to use for loading resources; can be + * {@code null} to use the default + * @return a {@link SpringFactoriesLoader} instance + * @since 6.0 + * @see #forResourceLocation(String) + */ + public static SpringFactoriesLoader forResourceLocation(@Nullable ClassLoader classLoader, String resourceLocation) { + Assert.hasText(resourceLocation, "'resourceLocation' must not be empty"); + Map loaders = SpringFactoriesLoader.cache.get(classLoader); + if (loaders == null) { + loaders = new ConcurrentReferenceHashMap<>(); + SpringFactoriesLoader.cache.put(classLoader, loaders); } - catch (Throwable ex) { - failureHandler.handleFailure(factoryType, factoryImplementationName, ex); - return null; + SpringFactoriesLoader loader = loaders.get(resourceLocation); + if (loader == null) { + loader = new SpringFactoriesLoader(classLoader, resourceLocation); + loaders.put(resourceLocation, loader); } + return loader; } @@ -302,6 +371,7 @@ public final class SpringFactoriesLoader { this.constructor = constructor; } + T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception { Object[] args = resolveArgs(argumentResolver); if (isKotlinType(this.constructor.getDeclaringClass())) { @@ -364,6 +434,7 @@ public final class SpringFactoriesLoader { } + /** * Inner class to avoid a hard dependency on Kotlin at runtime. */ @@ -535,6 +606,7 @@ public final class SpringFactoriesLoader { } + /** * Strategy for handling a failure that occurs when instantiating a factory. * 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 c171286faa..20640400d8 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 @@ -74,48 +74,48 @@ class SpringFactoriesLoaderTests { } @Test - void loadFactoriesWithNoRegisteredImplementations() { - List factories = SpringFactoriesLoader.loadFactories(Integer.class, null); + void loadWhenNoRegisteredImplementationsReturnsEmptyList() { + List factories = SpringFactoriesLoader.forDefaultResourceLocation().load(Integer.class); assertThat(factories).isEmpty(); } @Test - void loadFactoriesInCorrectOrderWithDuplicateRegistrationsPresent() { - List factories = SpringFactoriesLoader.loadFactories(DummyFactory.class, null); + void loadWhenDuplicateRegistrationsPresentReturnsListInCorrectOrder() { + List factories = SpringFactoriesLoader.forDefaultResourceLocation().load(DummyFactory.class); assertThat(factories).hasSize(2); assertThat(factories.get(0)).isInstanceOf(MyDummyFactory1.class); assertThat(factories.get(1)).isInstanceOf(MyDummyFactory2.class); } @Test - void loadPackagePrivateFactory() { + void loadWhenPackagePrivateFactory() { List factories = - SpringFactoriesLoader.loadFactories(DummyPackagePrivateFactory.class, null); + SpringFactoriesLoader.forDefaultResourceLocation().load(DummyPackagePrivateFactory.class); assertThat(factories).hasSize(1); assertThat(Modifier.isPublic(factories.get(0).getClass().getModifiers())).isFalse(); } @Test - void attemptToLoadFactoryOfIncompatibleType() { + void loadWhenIncompatibleTypeThrowsException() { assertThatIllegalArgumentException() - .isThrownBy(() -> SpringFactoriesLoader.loadFactories(String.class, null)) + .isThrownBy(() -> SpringFactoriesLoader.forDefaultResourceLocation().load(String.class)) .withMessageContaining("Unable to instantiate factory class " + "[org.springframework.core.io.support.MyDummyFactory1] for factory type [java.lang.String]"); } @Test - void attemptToLoadFactoryOfIncompatibleTypeWithLoggingFailureHandler() { + void loadWithLoggingFailureHandlerWhenIncompatibleTypeReturnsEmptyList() { Log logger = mock(Log.class); FailureHandler failureHandler = FailureHandler.logging(logger); - List factories = SpringFactoriesLoader.loadFactories(String.class, null, failureHandler); + List factories = SpringFactoriesLoader.forDefaultResourceLocation().load(String.class, failureHandler); assertThat(factories).isEmpty(); } @Test - void loadFactoryWithNonDefaultConstructor() { + void loadWithArgumentResolverWhenNoDefaultConstructor() { ArgumentResolver resolver = ArgumentResolver.of(String.class, "injected"); - List factories = SpringFactoriesLoader.loadFactories(DummyFactory.class, - LimitedClassLoader.constructorArgumentFactories, resolver); + List factories = SpringFactoriesLoader.forDefaultResourceLocation(LimitedClassLoader.constructorArgumentFactories) + .load(DummyFactory.class, resolver); assertThat(factories).hasSize(3); assertThat(factories.get(0)).isInstanceOf(MyDummyFactory1.class); assertThat(factories.get(1)).isInstanceOf(MyDummyFactory2.class); @@ -124,27 +124,51 @@ class SpringFactoriesLoaderTests { } @Test - void loadFactoryWithMultipleConstructors() { + void loadWhenMultipleConstructorsThrowsException() { ArgumentResolver resolver = ArgumentResolver.of(String.class, "injected"); assertThatIllegalArgumentException() - .isThrownBy(() -> SpringFactoriesLoader.loadFactories(DummyFactory.class, - LimitedClassLoader.multipleArgumentFactories, resolver)) + .isThrownBy(() -> SpringFactoriesLoader.forDefaultResourceLocation(LimitedClassLoader.multipleArgumentFactories) + .load(DummyFactory.class, resolver)) .withMessageContaining("Unable to instantiate factory class " + "[org.springframework.core.io.support.MultipleConstructorArgsDummyFactory] for factory type [org.springframework.core.io.support.DummyFactory]") .havingRootCause().withMessageContaining("Class [org.springframework.core.io.support.MultipleConstructorArgsDummyFactory] has no suitable constructor"); } @Test - void loadFactoryWithMissingArgumentUsingLoggingFailureHandler() { + void loadWithLoggingFailureHandlerWhenMissingArgumentDropsItem() { Log logger = mock(Log.class); FailureHandler failureHandler = FailureHandler.logging(logger); - List factories = SpringFactoriesLoader.loadFactories( - DummyFactory.class, LimitedClassLoader.multipleArgumentFactories, failureHandler); + List factories = SpringFactoriesLoader.forDefaultResourceLocation(LimitedClassLoader.multipleArgumentFactories) + .load(DummyFactory.class, failureHandler); assertThat(factories).hasSize(2); assertThat(factories.get(0)).isInstanceOf(MyDummyFactory1.class); assertThat(factories.get(1)).isInstanceOf(MyDummyFactory2.class); } + @Test + void loadFactoriesLoadsFromDefaultLocation() { + List factories = SpringFactoriesLoader.loadFactories( + DummyFactory.class, null); + assertThat(factories).hasSize(2); + assertThat(factories.get(0)).isInstanceOf(MyDummyFactory1.class); + assertThat(factories.get(1)).isInstanceOf(MyDummyFactory2.class); + } + + @Test + void loadForResourceLocationWhenLocationDoesNotExistReturnsEmptyList() { + List factories = SpringFactoriesLoader.forResourceLocation( + "META-INF/missing/missing-spring.factories").load(DummyFactory.class); + assertThat(factories).isEmpty(); + } + + @Test + void loadForResourceLocationLoadsFactories() { + List factories = SpringFactoriesLoader.forResourceLocation( + "META-INF/custom/custom-spring.factories").load(DummyFactory.class); + assertThat(factories).hasSize(1); + assertThat(factories.get(0)).isInstanceOf(MyDummyFactory1.class); + } + @Nested class FailureHandlerTests { diff --git a/spring-core/src/test/resources/META-INF/custom/custom-spring.factories b/spring-core/src/test/resources/META-INF/custom/custom-spring.factories new file mode 100644 index 0000000000..93be62e032 --- /dev/null +++ b/spring-core/src/test/resources/META-INF/custom/custom-spring.factories @@ -0,0 +1,2 @@ +org.springframework.core.io.support.DummyFactory =\ +org.springframework.core.io.support.MyDummyFactory1 diff --git a/spring-core/src/test/resources/META-INF/spring/org.springframework.core.io.support.SpringFactoriesLoaderTests/test.factories b/spring-core/src/test/resources/META-INF/spring/org.springframework.core.io.support.SpringFactoriesLoaderTests/test.factories new file mode 100644 index 0000000000..93be62e032 --- /dev/null +++ b/spring-core/src/test/resources/META-INF/spring/org.springframework.core.io.support.SpringFactoriesLoaderTests/test.factories @@ -0,0 +1,2 @@ +org.springframework.core.io.support.DummyFactory =\ +org.springframework.core.io.support.MyDummyFactory1