Spring Boot实践记录

Spring Boot factories机制

2020-03-02  本文已影响0人  Jeff_tian

Spring factories的加载方式类似于SPI

  1. 都是在顶层jar包中定义接口规范
  2. 具体接口实现交给项目按需加载
  3. 通过配置文件(spring.factories),定义对应接口的具体实现类
  4. 都是通过线程上下文类加载器的方式来加载具体的实现类
  5. 关于SPI的机制可以参考SPI加载机制和线程上下文类加载器

Spring Boot中具体的实现

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.  //获取线程上下文类加载器
     ClassLoader classLoader = getClassLoader();
    
  2.  //使用类加载器获取对应接口实现类的binary name,具体的实现在SpringFactoriesLoader类中完成。
     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);
            }
        }
    
    }
    
    //通过此处具体定义了spring factories配置文件的路径位于`META-INF/spring.factories`
    public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
    
    Spring boot在启动的时候,默认会有三个spring.factories文件,分别位于
    • spring-boot
    • spring-actuator-autoconfigure
    • spring-beans
  3. spring.factories具体内容
    # 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
    
    这一段配置是关于Application Context Initializers,对应的接口是org.springframework.context.ApplicationContextInitializer,下边为该接口对应的实现类。
    spring.factories的格式为:
    • Key 是接口全限定Class name;
    • Value 是key对应实现类的全限定Class name,用逗号分隔。
  4.  //创建对应实现类的实例
     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获取到的具体实现类的binary name
        for (String name : names) {
            try {
                //通过之前获取到的线程上下文类加载器去加载对应的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;
    }
    
上一篇 下一篇

猜你喜欢

热点阅读