【SpringBoot】【一】 加载初始化器、监听器详解

发布时间 2023-05-04 16:00:50作者: 酷酷-

1  前言

本节主要讲下 SpringBoot 启动的时候,加载初始化器、监听器的过程哈。

2  加载时机

我们先来看下加载的时机,也就是什么时候加载的呢,就是我们 SpringBoot启动的时候,创建 SpringApplication的时候就会去加载的,我们看下:

@SpringBootApplication
public class DemoApplication {
        // 我们的启动类
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

}
public SpringApplication(Class<?>... primarySources) {
    // 调用重载方法
    this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
    this.resourceLoader = resourceLoader;
    Assert.notNull(primarySources, "PrimarySources must not be null");
    // 我们的主类就是 DemoApplication.class
    this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
    // 决定当前应用程序的容器,默认使用的是Servlet容器
    this.webApplicationType = WebApplicationType.deduceFromClasspath();
    // 加载所有的初始化器
    setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
    // 加载所有的监听器
    setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
    // 这个方法仅仅是找到main方法所在的类,为后面的扫包作准备
    this.mainApplicationClass = deduceMainApplicationClass();
}

如上哈,可以看到加载初始化器和监听器都是会调用  getSpringFactoriesInstances 方法来记载的只是传入的类不一样哈,那么我们本节就重点看下这个方法哈。

3  getSpringFactoriesInstances 

我们直接看 getSpringFactoriesInstances 方法的源码:

// type是什么?就是我们要加载的类型
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    // 调用重载方法
    return getSpringFactoriesInstances(type, new Class<?>[] {});
}
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 集合保证类唯一,通过 SpringFactoriesLoader 来获取指定目录下的类信息
    // 那我们重点看下这个 SpringFactoriesLoader
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 通过全类名和类加载器去加载类并创建实例对象出来
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    // 排序器进行排序 排序器排序就不说了哈,这玩意有几种类型的优先级等,我在AOP的时候说过了哈,大家可以看我AOP的通知器顺序也涉及到这个排序器了
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
}

可以看到大致的过程:

(1)获取类加载器

(2)通过 SpringFactoriesLoader 来获取指定类型的全类名集合

(3)通过 createSpringFactoriesInstances 方法来加载类并创建类型的实例

(4)排序

那我们重点看下步骤2、3哈。

3.1  SpringFactoriesLoader.loadFactoryNames 获取指定类型的类集合

首先 SpringFactoriesLoader 这个类是 Spring的核心包里的类哈,我们进去看看:

/**
 * Load the fully qualified class names of factory implementations of the
 * given type from {@value #FACTORIES_RESOURCE_LOCATION}, using the given
 * class loader.
 * <p>As of Spring Framework 5.3, if a particular implementation class name
 * is discovered more than once for the given factory type, duplicates will
 * be ignored.
 * @param factoryType 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<?> factoryType, @Nullable ClassLoader classLoader) {
    ClassLoader classLoaderToUse = classLoader;
    // 类加载器为空的话,就用当前类的类加载器,说实话对类加载器没什么太大感觉因为这玩意感觉很底层,咱也摸不准是来区分什么的哈 有知道的麻烦告下哈
    if (classLoaderToUse == null) {
        classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
    }
    String factoryTypeName = factoryType.getName();
    // 调用 loadSpringFactories 方法
    return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
    /**
     * static final Map<ClassLoader, Map<String, List<String>>> cache = new ConcurrentReferenceHashMap<>();
     * 先从缓存中获取 key是类加载器
     */
    Map<String, List<String>> result = cache.get(classLoader);
    // 缓存中有就直接返回
    if (result != null) {
        return result;
    }
    // 初始化Map
    result = new HashMap<>();
    try {
        /**
         * public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
         * classLoader.getResources 加载当前类加载器的META-INF/spring.factories
         */
        Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                String factoryTypeName = ((String) entry.getKey()).trim();
                // 逗号分隔出来每个全类名
                String[] factoryImplementationNames =
                        StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
                for (String factoryImplementationName : factoryImplementationNames) {
                    // 塞进结果中
                    result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
                            .add(factoryImplementationName.trim());
                }
            }
        }
        // Replace all lists with unmodifiable lists containing unique elements
        /**
         * 去重
         * collectingAndThen 后边的能看懂么 就是去重后 toList收集到List中,然后将List放进 Collections.unmodifiableList的方法中,将集合变为只读的集合
         */
        result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
                .collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
        cache.put(classLoader, result);
    }
    catch (IOException ex) {
        throw new IllegalArgumentException("Unable to load factories from location [" +
                FACTORIES_RESOURCE_LOCATION + "]", ex);
    }
    return result;
}

可以看到哈,其实最后就是用到了类加载器的资源定位来获取指定目录文件下的内容的哈。

3.2  createSpringFactoriesInstances 加载以及实例化类

这个方法就处在 SpringApplication中,我们来看下:

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
        ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    for (String name : names) {
        try {
            // 加载指定的类
            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;
}

4  小结

好了,关于加载初始化器、监听器我们就看到这里哈,有理解不对的地方欢迎指正哈。