首页 > 编程语言 >深入浅出:SpringBoot启动流程源码分析(持续更新中......)最新日期:2024年10月29日

深入浅出:SpringBoot启动流程源码分析(持续更新中......)最新日期:2024年10月29日

时间:2024-10-29 22:44:46浏览次数:3  
标签:10 getSpringFactoriesInstances SpringBoot spring classLoader ApplicationContextI

Hello,大家好,我是此林。

今天来深入底层讲一讲SpringBoot是如何启动的,也就是我们单击运行SpringBoot启动类,它底层发生了什么?

SpringBoot启动类很简单,只有一行代码。

我们点进run() 方法。

我们发现,它底层其实进行了两步操作。

第一步是new出一个SpringApplication对象,第二个是执行run() 方法。

我们先看看是如何new SpringApplication的。

继续点进SpringApplication的构造方法。

可以看到,这里初始化设置了非常多的属性。

我们主要最后几行关键代码。

this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();

通过DEBUG打断点我们发现,this.primarySources 就是当前启动类com.itheima.test.App。

也就是我们之前之前启动类里传入的参数App.class本身。

继续执行程序,这里判断了应用的类型。这里是servlet类型(传统Web应用),此外还有None类型(非Web应用)、Reactive类型。

再看这两行代码。

this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));

1. 这里this.getSpringFactoriesInstances(ApplicationContextInitializer.class) 底层是从 META-INF/spring.factories 文件里找到实现了 ApplicationContextInitializer 接口的所有实现类。它们用于在 ApplicationContext 初始化之前进行一些自定义的初始化操作。

以下是spring.factories 文件:

2. this.getSpringFactoriesInstances(ApplicationListener.class) 也一样,底层是从 META-INF/spring.factories 文件里找到键名为 

org.springframework.context.ApplicationListener

的值,也就是找到所有实现了 ApplicationListener 接口的类。它们用于监听 Spring 应用上下文中的事件。

以下是相关的spring.factories 文件:

为了更易于理解,我们举个例子,再深入看看 this.getSpringFactoriesInstances(ApplicationContextInitializer.class) 源码。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
        // 返回了注册在META-INF/spring.factories文件中的ApplicationContextInitializer.class接口的所有实现类(一个全类名集合)
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 对每一个实现类进行实例化
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 对实例数组进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

这里有三步:

1. 获取注册在META-INF/spring.factories文件中的ApplicationContextInitializer.class接口的所有实现类(一个全类名集合)

2. 对每一个实现类进行实例化(返回一个实例数组)

3. 对实例数组进行排序,表示执行顺序。排序规则如下:如果实现了 PriorityOrdered 接口,优先级最高。如果实现了Ordered接口或者基于注解@Order,数值越小优先级越高。

点进AnnotationAwareOrderComparator.sort() 方法。发现它调用了java.util.List的 sort(Comparator<? super E> c) 方法。这里传进去的INSTANCE是 AnnotationAwareOrderComparator 本身,说明 AnnotationAwareOrderComparator 实现了Comparator 接口,重写了 compare() 方法。

但是我们又发现 AnnotationAwareOrderComparator 类里似乎没有实现Comparator 接口实现自定义排序。

我们查看AnnotationAwareOrderComparator继承关系链发现,它继承了OrderComparator类;OrderComparator又实现了Comparator接口,所以AnnotationAwareOrderComparator 间接实现了Comparator 接口来自定义排序。

public int compare(@Nullable Object o1, @Nullable Object o2) {
        return this.doCompare(o1, o2, (OrderSourceProvider)null);
    }

    private int doCompare(@Nullable Object o1, @Nullable Object o2, @Nullable OrderSourceProvider sourceProvider) {
        boolean p1 = o1 instanceof PriorityOrdered;
        boolean p2 = o2 instanceof PriorityOrdered;
        if (p1 && !p2) {
            return -1;
        } else if (p2 && !p1) {
            return 1;
        } else {
            int i1 = this.getOrder(o1, sourceProvider);
            int i2 = this.getOrder(o2, sourceProvider);
            return Integer.compare(i1, i2);
        }
    }

可以发现,主要功能是比较两个对象 o1 和 o2 的优先级顺序。
先检查 o1 和 o2 是否实现了 PriorityOrdered 接口。
如果 o1 实现了而 o2 没有实现,则 o1 的优先级更高,返回 -1。
如果 o2 实现了而 o1 没有实现,则 o2 的优先级更高,返回 1。
如果两者都实现了或都没有实现 PriorityOrdered 接口,则通过 getOrder 方法获取它们的顺序值,并比较这两个值。

我们再回到this.getSpringFactoriesInstances(ApplicationContextInitializer.class) 源码。

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
        // 返回了注册在META-INF/spring.factories文件中的ApplicationContextInitializer.class接口的所有实现类(一个全类名集合)
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        // 对每一个实现类进行实例化
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        // 对实例数组进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

我们再来详细地看看这行代码的底层,它是如何获取ApplicationContextInitializer接口的所有实现类的?

// 返回了注册在META-INF/spring.factories文件中的ApplicationContextInitializer.class接口的所有实现类(一个全类名集合)
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));

点进loadFactoryNames() 方法。

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
        ClassLoader classLoaderToUse = classLoader;
        if (classLoader == null) {
            classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
        }

        String factoryTypeName = factoryType.getName();
        return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
    }

这里 loadSpringFactories(classLoaderToUse) 返回的是一个 Map<String, List<String>> ,getOrDefault() 则是取到键为 factoryTypeName 的 List,取不到则返回空集合。那么factoryTypeName到底是什么?我们发现它就是 ApplicationContextInitializer 接口。

由于刚才我们看过spring.factories 文件了,它就是一个键值对文件,它的值也就是一个类名列表,所以我们推测 Map<String, List<String>> 就是整个spring.factories。

继续点进 loadSpringFactories() 方法。果然如此,详细请看注释。

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
        Map<String, List<String>> result = (Map)cache.get(classLoader);
        if (result != null) {
            return result;
        } else {
            Map<String, List<String>> result = new HashMap();

            try {
                // 加载出了所有META_INF/spring.factories文件
                Enumeration<URL> urls = classLoader.getResources("META-INF/spring.factories");
                // 遍历全部的META_INF/spring.factories文件
                while(urls.hasMoreElements()) {
                    URL url = (URL)urls.nextElement();
                    UrlResource resource = new UrlResource(url);
                    // 每一个META_INF/spring.factories文件相当于是一个properties文件
                    Properties properties = PropertiesLoaderUtils.loadProperties(resource);
                    Iterator var6 = properties.entrySet().iterator();
                    // 转成Map格式,key是接口名,value是实现类的列表
                    while(var6.hasNext()) {
                        Map.Entry<?, ?> entry = (Map.Entry)var6.next();
                        String factoryTypeName = ((String)entry.getKey()).trim();
                        String[] factoryImplementationNames = StringUtils.commaDelimitedListToStringArray((String)entry.getValue());
                        String[] var10 = factoryImplementationNames;
                        int var11 = factoryImplementationNames.length;

                        for(int var12 = 0; var12 < var11; ++var12) {
                            String factoryImplementationName = var10[var12];
                            ((List)result.computeIfAbsent(factoryTypeName, (key) -> {
                                return new ArrayList();
                            })).add(factoryImplementationName.trim());
                        }
                    }
                }

                result.replaceAll((factoryType, implementations) -> {
                    return (List)implementations.stream().distinct().collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
                });
                cache.put(classLoader, result);
                return result;
            } catch (IOException var14) {
                throw new IllegalArgumentException("Unable to load factories from location [META-INF/spring.factories]", var14);
            }
        }
    }

我们要扫描所有的所有META_INF/spring.factories文件,因为它可能不止一个。

我们再来总结一下,以上流程就是创建初始化SpringApplication对象,并设置相关属性。关键的还是这几行代码。

​
this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = new ArrayList(this.getSpringFactoriesInstances(BootstrapRegistryInitializer.class));        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();

​

至此,我们SpringApplication对象的创建就结束了。

明天我们再来详细讲一讲SpringApplication对象的run() 方法。

本文持续更新中....... 

关注我吧!老朋友此林,带你看不一样的世界!有疑惑的小伙伴可以私信我或者评论区留言,我看到后会及时回复!

往期文章:

SpringBoot自动配置原理:底层源码分析-CSDN博客

进阶版:深入浅出 Spring AOP底层原理分析(附自定义注解案例)(二)更新已完结-CSDN博客

标签:10,getSpringFactoriesInstances,SpringBoot,spring,classLoader,ApplicationContextI
From: https://blog.csdn.net/2401_82540083/article/details/143324130

相关文章

  • 10.28
    软件构造第八次作业 一.多选题(共3题,30分)1. (多选题)数据库管理系统的职责:A.数据管理B.数据存储C.数据组织D.数据加密正确答案: ABC:数据管理;数据存储;数据组织; 2. (多选题)关系完整性约束包括:A.参照完整性B.实体完整性C.用户定义完整性......
  • 10.29每日总结:《程序员修炼之道》读后感2
    经过这一阶段的阅读,我对程序员这个职业有了更深的理解和感悟。这本书强调了许多重要的理念和实践方法,让我认识到作为一名程序员,不能仅仅满足于编写代码,更要注重自身的修炼和成长。它提醒我们要保持对技术的好奇心,不断学习新的知识和技能,以适应快速变化的行业需求。书中提到的“......
  • 代码生产力提高100倍,Claude-3.5 +Cline 打造超强代码智能体!小白也能开发各种app!
    嘿,各位小伙伴们。今天,带大家走进神奇的AI世界,一起探索强大的工具和技术。最近,Anthropic发布了全新的Claude-3.5-sonnet模型,这可是Claude-3.5-sonnet模型的升级版哦!这款最新的模型在多方面的能力都有了显著提升,尤其是在编程方面。已经完全超越GPT模型,并且其训练数据的截......
  • 10.29 人工智能学习内容
    上节课内容补充【给大语言模型法阅读材料】如果你手边现成有原文,而且长度合适,建议自带原文去找大语言模型Usetheprovidedarticlesdelimitedbytriplequotestoanswerquestions.Iftheanswercannotbefoundinthearticles,write"Icouldnotfindananswer."......
  • SS241029C. 卡路里(calorie)
    SS241029C.卡路里(calorie)题意有\(m\)家奶茶店,一共有\(n\)种奶茶,每家店都有这\(n\)种奶茶。奶茶店排成一排,两两之间距离\(d_i\)。每家奶茶店每种奶茶有一个卡路里值\(a_{i,j}\)。选择若干家连续的奶茶店,在这些奶茶店中每种奶茶各喝一次,求最大化\((\sum_ja_j)-(s_r......
  • 2024.10.19 CF2030(Div.2)
    比赛链接Solved:5/8Upsolved:6/8Rank:166E.MEXmizetheScore题意定义一个集合的分数为:将它分成若干个子集,mex和的最大值。(mex从0开始算)给n个数,求所有非空子集的分数之和。\(n\leq2\times10^5\)题解对一个确定的集合,它的划分方式一定是每次分出去一个最长的{0,......
  • 2024.10.14 Codeforces Round 978 (Div. 2)
    比赛链接Solved:4/7Upsolved:5/7Rank:447(rated343)D2.Asesino(HardVersion)题意:有n个人,除了一个卧底以外,其他人或者只会说真话,或者只会说谎,且他们知道彼此的身份。卧底只会说谎,但其他人都认为他只会说真话。现在你可以进行若干次询问,每次询问形如问第i个人第j个人是什么......
  • 基于SpringBoot+Vue的社区居民诊疗健康服务平台设计与实现
    ......
  • 2024.10.24 The 2021 ICPC Northwestern Russia Regional Contest
    比赛链接Solved:8/14Penalty:909Rank:23前五道签到题ABCHL。K.KaleidoscopicRoute题意给一张带边权的图,求一条1到n的路径,使经过的边数最少的同时边的极差最大。题解求出最短路图,然后DAG上dp:f和g分别表示从1到这个点能经过的最大边权和最小边权。然后每转移一条边(x,y,z......
  • 0x02 Leetcode Hot100 哈希
    前置知识掌握每种语言的基本数据类型及其时间复杂度。Python:list、tuple、set、dictC++:STL中的vector、set、mapJava:集合类中的List、Set、Map为什么是哈希?在不同语言中,对于字典(dict)类的数据都会先将其键(key)进行哈希(Hash)运算,这个Hash值决定了键值对在内存中的存储位置,因此......