org.springframework.core.io.support.SpringFactoriesLoader Maven / Gradle / Ivy
/*
* Copyright 2002-2023 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.core.io.support;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;
import kotlin.jvm.JvmClassMappingKt;
import kotlin.reflect.KFunction;
import kotlin.reflect.KParameter;
import kotlin.reflect.full.KClasses;
import kotlin.reflect.jvm.KCallablesJvm;
import kotlin.reflect.jvm.ReflectJvmMapping;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.KotlinDetector;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.io.UrlResource;
import org.springframework.core.log.LogMessage;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;
/**
* General purpose factory loading mechanism for internal use within the framework.
*
* {@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
* factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
* may be present in multiple JAR files in the classpath. The {@code spring.factories}
* file must be in {@link Properties} format, where the key is the fully qualified
* name of the interface or abstract class, and the value is a comma-separated list of
* implementation class names. For example:
*
*
example.MyService=example.MyServiceImpl1,example.MyServiceImpl2
*
* where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
* and {@code MyServiceImpl2} are two implementations.
*
* Implementation classes must have a single resolvable constructor that will
* be used to create the instance, either:
*
* - a primary or single constructor
* - a single public constructor
* - the default constructor
*
*
* If the resolvable constructor has arguments, a suitable {@link ArgumentResolver
* ArgumentResolver} should be provided. To customize how instantiation failures
* are handled, consider providing a {@link FailureHandler FailureHandler}.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
* @author Sam Brannen
* @author Andy Wilkinson
* @author Madhura Bhave
* @author Phillip Webb
* @since 3.2
*/
public class SpringFactoriesLoader {
/**
* The location to look for factories.
*
Can be present in multiple JAR files.
*/
public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
private static final FailureHandler THROWING_FAILURE_HANDLER = FailureHandler.throwing();
private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
static final Map> cache = new ConcurrentReferenceHashMap<>();
@Nullable
private final ClassLoader classLoader;
private final Map> factories;
/**
* 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
* @since 6.0
*/
protected SpringFactoriesLoader(@Nullable ClassLoader classLoader, Map> factories) {
this.classLoader = classLoader;
this.factories = factories;
}
/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the configured class loader
* and a default argument resolver that expects a no-arg constructor.
* The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
*
If a custom instantiation strategy is required, use {@code load(...)}
* with a custom {@link ArgumentResolver ArgumentResolver} and/or
* {@link FailureHandler FailureHandler}.
*
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.
* @param factoryType the interface or abstract class representing the factory
* @throws IllegalArgumentException if any factory implementation class cannot
* be loaded or if an error occurs while instantiating any factory
* @since 6.0
*/
public List load(Class factoryType) {
return load(factoryType, null, null);
}
/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the configured class loader
* and the given argument resolver.
* 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.
* @param factoryType the interface or abstract class representing the factory
* @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 List load(Class factoryType, @Nullable ArgumentResolver argumentResolver) {
return load(factoryType, argumentResolver, null);
}
/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the configured class loader
* with custom failure handling provided by the given failure handler.
* 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 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 failureHandler strategy used to handle factory instantiation failures
* @since 6.0
*/
public List load(Class factoryType, @Nullable FailureHandler failureHandler) {
return load(factoryType, null, failureHandler);
}
/**
* Load and instantiate the factory implementations of the given type from
* {@value #FACTORIES_RESOURCE_LOCATION}, using the configured class loader,
* the given argument resolver, and custom failure handling provided by the given
* failure handler.
* 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 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 argumentResolver strategy used to resolve constructor arguments by their type
* @param failureHandler strategy used to handle factory instantiation failures
* @since 6.0
*/
public List load(Class factoryType, @Nullable ArgumentResolver argumentResolver,
@Nullable FailureHandler failureHandler) {
Assert.notNull(factoryType, "'factoryType' must not be null");
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);
}
}
AnnotationAwareOrderComparator.sort(result);
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 [%s] is not assignable to factory type [%s]".formatted(implementationName, 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
* class loader.
* As of Spring Framework 5.3, if a particular implementation class name
* is discovered more than once for the given factory type, duplicates will
* be ignored.
* @param factoryType the interface or abstract class representing the factory
* @param classLoader the ClassLoader to use for loading resources; can be
* {@code null} to use the default
* @throws IllegalArgumentException if an error occurs while loading factory names
* @see #loadFactories
* @deprecated as of 6.0 in favor of {@link #load(Class, ArgumentResolver, FailureHandler)}
*/
@Deprecated(since = "6.0")
public static List loadFactoryNames(Class factoryType, @Nullable ClassLoader classLoader) {
return forDefaultResourceLocation(classLoader).loadFactoryNames(factoryType);
}
/**
* Create 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);
}
/**
* Create 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(FACTORIES_RESOURCE_LOCATION, classLoader);
}
/**
* Create a {@link SpringFactoriesLoader} instance that will load and
* instantiate the factory implementations from the given location,
* using the default class loader.
* @param resourceLocation the resource location to look for factories
* @return a {@link SpringFactoriesLoader} instance
* @since 6.0
* @see #forResourceLocation(String, ClassLoader)
*/
public static SpringFactoriesLoader forResourceLocation(String resourceLocation) {
return forResourceLocation(resourceLocation, null);
}
/**
* Create a {@link SpringFactoriesLoader} instance that will load and
* instantiate the factory implementations from the given location,
* using the given class loader.
* @param resourceLocation the resource location to look for factories
* @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(String resourceLocation, @Nullable ClassLoader classLoader) {
Assert.hasText(resourceLocation, "'resourceLocation' must not be empty");
ClassLoader resourceClassLoader = (classLoader != null ? classLoader :
SpringFactoriesLoader.class.getClassLoader());
Map loaders = cache.computeIfAbsent(
resourceClassLoader, key -> new ConcurrentReferenceHashMap<>());
return loaders.computeIfAbsent(resourceLocation, key ->
new SpringFactoriesLoader(classLoader, loadFactoriesResource(resourceClassLoader, resourceLocation)));
}
protected 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) -> {
String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String) value);
List implementations = result.computeIfAbsent(((String) name).trim(),
key -> new ArrayList<>(factoryImplementationNames.length));
Arrays.stream(factoryImplementationNames).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.
* @since 6.0
* @param the instance implementation type
*/
static final class FactoryInstantiator {
private final Constructor constructor;
private FactoryInstantiator(Constructor constructor) {
ReflectionUtils.makeAccessible(constructor);
this.constructor = constructor;
}
T instantiate(@Nullable ArgumentResolver argumentResolver) throws Exception {
Object[] args = resolveArgs(argumentResolver);
if (isKotlinType(this.constructor.getDeclaringClass())) {
return KotlinDelegate.instantiate(this.constructor, args);
}
return this.constructor.newInstance(args);
}
private Object[] resolveArgs(@Nullable ArgumentResolver argumentResolver) {
Class[] types = this.constructor.getParameterTypes();
return (argumentResolver != null ?
Arrays.stream(types).map(argumentResolver::resolve).toArray() :
new Object[types.length]);
}
@SuppressWarnings("unchecked")
static FactoryInstantiator forClass(Class factoryImplementationClass) {
Constructor constructor = findConstructor(factoryImplementationClass);
Assert.state(constructor != null, () ->
"Class [%s] has no suitable constructor".formatted(factoryImplementationClass.getName()));
return new FactoryInstantiator<>((Constructor) constructor);
}
@Nullable
private static Constructor findConstructor(Class factoryImplementationClass) {
// Same algorithm as BeanUtils.getResolvableConstructor
Constructor constructor = findPrimaryKotlinConstructor(factoryImplementationClass);
constructor = (constructor != null ? constructor :
findSingleConstructor(factoryImplementationClass.getConstructors()));
constructor = (constructor != null ? constructor :
findSingleConstructor(factoryImplementationClass.getDeclaredConstructors()));
constructor = (constructor != null ? constructor :
findDeclaredConstructor(factoryImplementationClass));
return constructor;
}
@Nullable
private static Constructor findPrimaryKotlinConstructor(Class factoryImplementationClass) {
return (isKotlinType(factoryImplementationClass) ?
KotlinDelegate.findPrimaryConstructor(factoryImplementationClass) : null);
}
private static boolean isKotlinType(Class factoryImplementationClass) {
return KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(factoryImplementationClass);
}
@Nullable
private static Constructor findSingleConstructor(Constructor[] constructors) {
return (constructors.length == 1 ? constructors[0] : null);
}
@Nullable
private static Constructor findDeclaredConstructor(Class factoryImplementationClass) {
try {
return factoryImplementationClass.getDeclaredConstructor();
}
catch (NoSuchMethodException ex) {
return null;
}
}
}
/**
* Nested class to avoid a hard dependency on Kotlin at runtime.
* @since 6.0
*/
private static class KotlinDelegate {
@Nullable
static Constructor findPrimaryConstructor(Class clazz) {
try {
KFunction primaryConstructor = KClasses.getPrimaryConstructor(JvmClassMappingKt.getKotlinClass(clazz));
if (primaryConstructor != null) {
Constructor constructor = ReflectJvmMapping.getJavaConstructor(
primaryConstructor);
Assert.state(constructor != null, () ->
"Failed to find Java constructor for Kotlin primary constructor: " + clazz.getName());
return constructor;
}
}
catch (UnsupportedOperationException ex) {
// ignore
}
return null;
}
static T instantiate(Constructor constructor, Object[] args) throws Exception {
KFunction kotlinConstructor = ReflectJvmMapping.getKotlinFunction(constructor);
if (kotlinConstructor == null) {
return constructor.newInstance(args);
}
makeAccessible(constructor, kotlinConstructor);
return instantiate(kotlinConstructor, convertArgs(args, kotlinConstructor.getParameters()));
}
private static void makeAccessible(Constructor constructor,
KFunction kotlinConstructor) {
if ((!Modifier.isPublic(constructor.getModifiers())
|| !Modifier.isPublic(constructor.getDeclaringClass().getModifiers()))) {
KCallablesJvm.setAccessible(kotlinConstructor, true);
}
}
private static Map convertArgs(Object[] args, List parameters) {
Map result = CollectionUtils.newHashMap(parameters.size());
Assert.isTrue(args.length <= parameters.size(),
"Number of provided arguments should be less than or equal to the number of constructor parameters");
for (int i = 0; i < args.length; i++) {
if (!parameters.get(i).isOptional() || args[i] != null) {
result.put(parameters.get(i), args[i]);
}
}
return result;
}
private static T instantiate(KFunction kotlinConstructor, Map args) {
return kotlinConstructor.callBy(args);
}
}
/**
* Strategy for resolving constructor arguments based on their type.
* @since 6.0
* @see ArgumentResolver#of(Class, Object)
* @see ArgumentResolver#ofSupplied(Class, Supplier)
* @see ArgumentResolver#from(Function)
*/
@FunctionalInterface
public interface ArgumentResolver {
/**
* Resolve the given argument if possible.
* @param the argument type
* @param type the argument type
* @return the resolved argument value or {@code null}
*/
@Nullable
T resolve(Class type);
/**
* Create a new composed {@link ArgumentResolver} by combining this resolver
* with the given type and value.
* @param the argument type
* @param type the argument type
* @param value the argument value
* @return a new composite {@link ArgumentResolver} instance
*/
default ArgumentResolver and(Class type, T value) {
return and(ArgumentResolver.of(type, value));
}
/**
* Create a new composed {@link ArgumentResolver} by combining this resolver
* with the given type and value.
* @param the argument type
* @param type the argument type
* @param valueSupplier the argument value supplier
* @return a new composite {@link ArgumentResolver} instance
*/
default ArgumentResolver andSupplied(Class type, Supplier valueSupplier) {
return and(ArgumentResolver.ofSupplied(type, valueSupplier));
}
/**
* Create a new composed {@link ArgumentResolver} by combining this resolver
* with the given resolver.
* @param argumentResolver the argument resolver to add
* @return a new composite {@link ArgumentResolver} instance
*/
default ArgumentResolver and(ArgumentResolver argumentResolver) {
return from(type -> {
Object resolved = resolve(type);
return (resolved != null ? resolved : argumentResolver.resolve(type));
});
}
/**
* Factory method that returns an {@link ArgumentResolver} that always
* returns {@code null}.
* @return a new {@link ArgumentResolver} instance
*/
static ArgumentResolver none() {
return from(type -> null);
}
/**
* Factory method that can be used to create an {@link ArgumentResolver}
* that resolves only the given type.
* @param the argument type
* @param type the argument type
* @param value the argument value
* @return a new {@link ArgumentResolver} instance
*/
static ArgumentResolver of(Class type, T value) {
return ofSupplied(type, () -> value);
}
/**
* Factory method that can be used to create an {@link ArgumentResolver}
* that resolves only the given type.
* @param the argument type
* @param type the argument type
* @param valueSupplier the argument value supplier
* @return a new {@link ArgumentResolver} instance
*/
static ArgumentResolver ofSupplied(Class type, Supplier valueSupplier) {
return from(candidateType -> (candidateType.equals(type) ? valueSupplier.get() : null));
}
/**
* Factory method that creates a new {@link ArgumentResolver} from a
* lambda friendly function. The given function is provided with the
* argument type and must provide an instance of that type or {@code null}.
* @param function the resolver function
* @return a new {@link ArgumentResolver} instance backed by the function
*/
static ArgumentResolver from(Function, Object> function) {
return new ArgumentResolver() {
@SuppressWarnings("unchecked")
@Override
public T resolve(Class type) {
return (T) function.apply(type);
}
};
}
}
/**
* Strategy for handling a failure that occurs when instantiating a factory.
* @since 6.0
* @see FailureHandler#throwing()
* @see FailureHandler#logging(Log)
*/
@FunctionalInterface
public interface FailureHandler {
/**
* Handle the {@code failure} that occurred when instantiating the
* {@code factoryImplementationName} that was expected to be of the
* given {@code factoryType}.
* @param factoryType the type of the factory
* @param factoryImplementationName the name of the factory implementation
* @param failure the failure that occurred
* @see #throwing()
* @see #logging
*/
void handleFailure(Class factoryType, String factoryImplementationName, Throwable failure);
/**
* Create a new {@link FailureHandler} that handles errors by throwing an
* {@link IllegalArgumentException}.
* @return a new {@link FailureHandler} instance
* @see #throwing(BiFunction)
*/
static FailureHandler throwing() {
return throwing(IllegalArgumentException::new);
}
/**
* Create a new {@link FailureHandler} that handles errors by throwing an
* exception.
* @param exceptionFactory factory used to create the exception
* @return a new {@link FailureHandler} instance
*/
static FailureHandler throwing(BiFunction exceptionFactory) {
return handleMessage((messageSupplier, failure) -> {
throw exceptionFactory.apply(messageSupplier.get(), failure);
});
}
/**
* Create a new {@link FailureHandler} that handles errors by logging trace
* messages.
* @param logger the logger used to log messages
* @return a new {@link FailureHandler} instance
*/
static FailureHandler logging(Log logger) {
return handleMessage((messageSupplier, failure) -> logger.trace(LogMessage.of(messageSupplier), failure));
}
/**
* Create a new {@link FailureHandler} that handles errors using a standard
* formatted message.
* @param messageHandler the message handler used to handle the problem
* @return a new {@link FailureHandler} instance
*/
static FailureHandler handleMessage(BiConsumer, Throwable> messageHandler) {
return (factoryType, factoryImplementationName, failure) -> {
Supplier messageSupplier = () -> "Unable to instantiate factory class [%s] for factory type [%s]"
.formatted(factoryImplementationName, factoryType.getName());
messageHandler.accept(messageSupplier, failure);
};
}
}
}