Spring Boot factories機(jī)制

Spring factories的加載方式類(lèi)似于SPI

  1. 都是在頂層jar包中定義接口規(guī)范
  2. 具體接口實(shí)現(xiàn)交給項(xiàng)目按需加載
  3. 通過(guò)配置文件(spring.factories),定義對(duì)應(yīng)接口的具體實(shí)現(xiàn)類(lèi)
  4. 都是通過(guò)線(xiàn)程上下文類(lèi)加載器的方式來(lái)加載具體的實(shí)現(xiàn)類(lèi)
  5. 關(guān)于SPI的機(jī)制可以參考SPI加載機(jī)制和線(xiàn)程上下文類(lèi)加載器

Spring Boot中具體的實(shí)現(xiàn)

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
    ClassLoader classLoader = getClassLoader();
    // Use names and ensure unique to protect against duplicates
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}
  1.  //獲取線(xiàn)程上下文類(lèi)加載器
     ClassLoader classLoader = getClassLoader();
    
  2.  //使用類(lèi)加載器獲取對(duì)應(yīng)接口實(shí)現(xiàn)類(lèi)的binary name,具體的實(shí)現(xiàn)在SpringFactoriesLoader類(lèi)中完成。
     SpringFactoriesLoader.loadFactoryNames(type, classLoader)
    
    /**
    * General purpose factory loading mechanism for internal use within the framework.
    *
    * <p>{@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:
    *
    * <pre class="code">example.MyService=example.MyServiceImpl1,example.MyServiceImpl2</pre>
    *
    * where {@code example.MyService} is the name of the interface, and {@code MyServiceImpl1}
    * and {@code MyServiceImpl2} are two implementations.
    *
    * @author Arjen Poutsma
    * @author Juergen Hoeller
    * @author Sam Brannen
    * @since 3.2
    */
    public final class SpringFactoriesLoader {
    
        /**
        * The location to look for factories.
        * <p>Can be present in multiple JAR files.
        */
        public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    
        private static final Log logger = LogFactory.getLog(SpringFactoriesLoader.class);
    
        private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
    
    
        private SpringFactoriesLoader() {
        }
    
    
        /**
        * Load and instantiate the factory implementations of the given type from
        * {@value #FACTORIES_RESOURCE_LOCATION}, using the given class loader.
        * <p>The returned factories are sorted through {@link AnnotationAwareOrderComparator}.
        * <p>If a custom instantiation strategy is required, use {@link #loadFactoryNames}
        * to obtain all registered factory names.
        * @param factoryClass 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
        * @see #loadFactoryNames
        */
        public static <T> List<T> loadFactories(Class<T> factoryClass, @Nullable ClassLoader classLoader) {
            Assert.notNull(factoryClass, "'factoryClass' must not be null");
            ClassLoader classLoaderToUse = classLoader;
            if (classLoaderToUse == null) {
                classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
            }
            List<String> factoryNames = loadFactoryNames(factoryClass, classLoaderToUse);
            if (logger.isTraceEnabled()) {
                logger.trace("Loaded [" + factoryClass.getName() + "] names: " + factoryNames);
            }
            List<T> result = new ArrayList<>(factoryNames.size());
            for (String factoryName : factoryNames) {
                result.add(instantiateFactory(factoryName, factoryClass, classLoaderToUse));
            }
            AnnotationAwareOrderComparator.sort(result);
            return result;
        }
    
        /**
        * Load the fully qualified class names of factory implementations of the
        * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
        * class loader.
        * @param factoryClass 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
        */
        public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
            String factoryClassName = factoryClass.getName();
            return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
        }
    
        private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
            MultiValueMap<String, String> result = cache.get(classLoader);
            if (result != null) {
                return result;
            }
    
            try {
                Enumeration<URL> urls = (classLoader != null ?
                        classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
                        ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
                result = new LinkedMultiValueMap<>();
                while (urls.hasMoreElements()) {
                    URL url = urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    for (Map.Entry<?, ?> entry : properties.entrySet()) {
                        String factoryClassName = ((String) entry.getKey()).trim();
                        for (String factoryName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
                            result.add(factoryClassName, factoryName.trim());
                        }
                    }
                }
                cache.put(classLoader, result);
                return result;
            }
            catch (IOException ex) {
                throw new IllegalArgumentException("Unable to load factories from location [" +
                        FACTORIES_RESOURCE_LOCATION + "]", ex);
            }
        }
    
        @SuppressWarnings("unchecked")
        private static <T> T instantiateFactory(String instanceClassName, Class<T> factoryClass, ClassLoader classLoader) {
            try {
                Class<?> instanceClass = ClassUtils.forName(instanceClassName, classLoader);
                if (!factoryClass.isAssignableFrom(instanceClass)) {
                    throw new IllegalArgumentException(
                            "Class [" + instanceClassName + "] is not assignable to [" + factoryClass.getName() + "]");
                }
                return (T) ReflectionUtils.accessibleConstructor(instanceClass).newInstance();
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Unable to instantiate factory class: " + factoryClass.getName(), ex);
            }
        }
    
    }
    
    //通過(guò)此處具體定義了spring factories配置文件的路徑位于`META-INF/spring.factories`
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    Spring boot在啟動(dòng)的時(shí)候,默認(rèn)會(huì)有三個(gè)spring.factories文件,分別位于
    • spring-boot
    • spring-actuator-autoconfigure
    • spring-beans
  3. spring.factories具體內(nèi)容
    # Application Context Initializers
    org.springframework.context.ApplicationContextInitializer=\
    org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
    org.springframework.boot.context.ContextIdApplicationContextInitializer,\
    org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
    org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
    
    這一段配置是關(guān)于Application Context Initializers,對(duì)應(yīng)的接口是org.springframework.context.ApplicationContextInitializer,下邊為該接口對(duì)應(yīng)的實(shí)現(xiàn)類(lèi)。
    spring.factories的格式為:
    • Key 是接口全限定Class name;
    • Value 是key對(duì)應(yīng)實(shí)現(xiàn)類(lèi)的全限定Class name,用逗號(hào)分隔。
  4.  //創(chuàng)建對(duì)應(yīng)實(shí)現(xiàn)類(lèi)的實(shí)例
     createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        //names就是剛才從SpringFactoriesLoader獲取到的具體實(shí)現(xiàn)類(lèi)的binary name
        for (String name : names) {
            try {
                //通過(guò)之前獲取到的線(xiàn)程上下文類(lèi)加載器去加載對(duì)應(yīng)的binary name
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            }
            catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
    
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容