001/*
002 * Copyright 2002-2018 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.lang.reflect.Constructor;
021import java.net.URL;
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Enumeration;
025import java.util.List;
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.util.Assert;
034import org.springframework.util.ClassUtils;
035import org.springframework.util.ReflectionUtils;
036import org.springframework.util.StringUtils;
037
038/**
039 * General purpose factory loading mechanism for internal use within the framework.
040 *
041 * <p>{@code SpringFactoriesLoader} {@linkplain #loadFactories loads} and instantiates
042 * factories of a given type from {@value #FACTORIES_RESOURCE_LOCATION} files which
043 * may be present in multiple JAR files in the classpath. The {@code spring.factories}
044 * file must be in {@link Properties} format, where the key is the fully qualified
045 * name of the interface or abstract class, and the value is a comma-separated list of
046 * implementation class names. For example:
047 *
048 * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
049 *
050 * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
051 * and {@code MyServiceImpl2} are two implementations.
052 *
053 * @author Arjen Poutsma
054 * @author Juergen Hoeller
055 * @author Sam Brannen
056 * @since 3.2
057 */
058public abstract class SpringFactoriesLoader {
059
060        /**
061         * The location to look for factories.
062         * <p>Can be present in multiple JAR files.
063         */
064        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
065
066        private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
067
068
069        /**
070         * Load and instantiate the factory implementations of the given type from
071         * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
072         * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
073         * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
074         * to obtain all registered factory names.
075         * @param factoryClass the interface or abstract class representing the factory
076         * @param classLoader the ClassLoader to use for loading (can be {@code null} to use the default)
077         * @throws IllegalArgumentException if any factory implementation class cannot
078         * be loaded or if an error occurs while instantiating any factory
079         * @see #loadFactoryNames
080         */
081        public static <T> List<T> loadFactories(Class<T> factoryClass, ClassLoader classLoader) {
082                Assert.notNull(factoryClass, "'factoryClass' must not be null");
083                ClassLoader classLoaderToUse = classLoader;
084                if (classLoaderToUse == null) {
085                        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
086                }
087                List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
088                if (logger.isTraceEnabled()) {
089                        logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
090                }
091                List<T> result = new ArrayList<T>(factoryNames.size());
092                for (String factoryName : factoryNames) {
093                        result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
094                }
095                AnnotationAwareOrderComparator.sort(result);
096                return result;
097        }
098
099        /**
100         * Load the fully qualified class names of factory implementations of the
101         * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
102         * class loader.
103         * @param factoryClass the interface or abstract class representing the factory
104         * @param classLoader the ClassLoader to use for loading resources; can be
105         * {@code null} to use the default
106         * @throws IllegalArgumentException if an error occurs while loading factory names
107         * @see #loadFactories
108         */
109        public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
110                String factoryClassName = factoryClass.getName();
111                try {
112                        Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
113                                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
114                        List<String> result = new ArrayList<String>();
115                        while (urls.hasMoreElements()) {
116                                URL url = urls.nextElement();
117                                Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
118                                String propertyValue = properties.getProperty(factoryClassName);
119                                for (String factoryName : StringUtils.commaDelimitedListToStringArray(propertyValue)) {
120                                        result.add(factoryName.trim());
121                                }
122                        }
123                        return result;
124                }
125                catch (IOException ex) {
126                        throw new IllegalArgumentException("Unable to load factories from location [" +
127                                        FACTORIES_RESOURCE_LOCATION + "]", ex);
128                }
129        }
130
131        @SuppressWarnings("unchecked")
132        private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
133                try {
134                        Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
135                        if (!factoryClass.isAssignableFrom(instanceClass)) {
136                                throw new IllegalArgumentException(
137                                                "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
138                        }
139                        Constructor<?> constructor = instanceClass.getDeclaredConstructor();
140                        ReflectionUtils.makeAccessible(constructor);
141                        return (T) constructor.newInstance();
142                }
143                catch (Throwable ex) {
144                        throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
145                }
146        }
147
148}