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}