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}