001/*
002 * Copyright 2002-2019 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.core.io.support;
018
019import java.io.IOException;
020import java.net.URL;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.Enumeration;
024import java.util.List;
025import java.util.Map;
026import java.util.Properties;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030
031import org.springframework.core.annotation.AnnotationAwareOrderComparator;
032import org.springframework.core.io.UrlResource;
033import org.springframework.lang.Nullable;
034import org.springframework.util.Assert;
035import org.springframework.util.ClassUtils;
036import org.springframework.util.ConcurrentReferenceHashMap;
037import org.springframework.util.LinkedMultiValueMap;
038import org.springframework.util.MultiValueMap;
039import org.springframework.util.ReflectionUtils;
040import org.springframework.util.StringUtils;
041
042/**
043 * General purpose factory loading mechanism for internal use within the framework.
044 *
045 * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
046 * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
047 * may be present in multiple JAR files in the classpath. The {@code spring.factories}
048 * file must be in {@link Properties} format, where the key is the fully qualified
049 * name of the interface or abstract class, and the value is a comma-separated list of
050 * implementation class names. For example:
051 *
052 * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
053 *
054 * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
055 * and {@code MyServiceImpl2} are two implementations.
056 *
057 * @author Arjen Poutsma
058 * @author Juergen Hoeller
059 * @author Sam Brannen
060 * @since 3.2
061 */
062public final class SpringFactoriesLoader {
063
064        /**
065         * The location to look for factories.
066         * <p>Can be present in multiple JAR files.
067         */
068        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
069
070
071        private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
072
073        private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
074
075
076        private SpringFactoriesLoader() {
077        }
078
079
080        /**
081         * Load and instantiate the factory implementations of the given type from
082         * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
083         * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
084         * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
085         * to obtain all registered factory names.
086         * @param factoryType the interface or abstract class representing the factory
087         * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
088         * @throws IllegalArgumentException if any factory implementation class cannot
089         * be loaded or if an error occurs while instantiating any factory
090         * @see #loadFactoryNames
091         */
092        public static <T> List<T> loadFactories(Class<T> factoryType, @Nullable ClassLoader classLoader) {
093                Assert.notNull(factoryType, "'factoryType' must not be null");
094                ClassLoader classLoaderToUse = classLoader;
095                if (classLoaderToUse == null) {
096                        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
097                }
098                List<String> factoryImplementationNames = loadFactoryNames(factoryType, classLoaderToUse);
099                if (logger.isTraceEnabled()) {
100                        logger.trace("Loaded [" + factoryType.getName() + "] names: " + factoryImplementationNames);
101                }
102                List<T> result = new ArrayList<>(factoryImplementationNames.size());
103                for (String factoryImplementationName : factoryImplementationNames) {
104                        result.add(instantiateFactory(factoryImplementationName, factoryType, classLoaderToUse));
105                }
106                AnnotationAwareOrderComparator.sort(result);
107                return result;
108        }
109
110        /**
111         * Load the fully qualified class names of factory implementations of the
112         * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
113         * class loader.
114         * @param factoryType the interface or abstract class representing the factory
115         * @param classLoader the ClassLoader to use for loading resources; can be
116         * {@code null} to use the default
117         * @throws IllegalArgumentException if an error occurs while loading factory names
118         * @see #loadFactories
119         */
120        public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
121                String factoryTypeName = factoryType.getName();
122                return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
123        }
124
125        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
126                MultiValueMap<String, String> result = cache.get(classLoader);
127                if (result != null) {
128                        return result;
129                }
130
131                try {
132                        Enumeration<URL> urls = (classLoader != null ?
133                                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
134                                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
135                        result = new LinkedMultiValueMap<>();
136                        while (urls.hasMoreElements()) {
137                                URL url = urls.nextElement();
138                                UrlResource resource = new UrlResource(url);
139                                Properties properties = PropertiesLoaderUtils.loadProperties(resource);
140                                for (Map.Entry<?, ?> entry : properties.entrySet()) {
141                                        String factoryTypeName = ((String) entry.getKey()).trim();
142                                        for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
143                                                result.add(factoryTypeName, factoryImplementationName.trim());
144                                        }
145                                }
146                        }
147                        cache.put(classLoader, result);
148                        return result;
149                }
150                catch (IOException ex) {
151                        throw new IllegalArgumentException("Unable to load factories from location [" +
152                                        FACTORIES_RESOURCE_LOCATION + "]", ex);
153                }
154        }
155
156        @SuppressWarnings("unchecked")
157        private static <T> T instantiateFactory(String factoryImplementationName, Class<T> factoryType, ClassLoader classLoader) {
158                try {
159                        Class<?> factoryImplementationClass = ClassUtils.forName(factoryImplementationName, classLoader);
160                        if (!factoryType.isAssignableFrom(factoryImplementationClass)) {
161                                throw new IllegalArgumentException(
162                                                "Class [" + factoryImplementationName + "] is not assignable to factory type [" + factoryType.getName() + "]");
163                        }
164                        return (T) ReflectionUtils.accessibleConstructor(factoryImplementationClass).newInstance();
165                }
166                catch (Throwable ex) {
167                        throw new IllegalArgumentException(
168                                "Unable to instantiate factory class [" + factoryImplementationName + "] for factory type [" + factoryType.getName() + "]",
169                                ex);
170                }
171        }
172
173}