如何从 Spring-Boot 中的 spring.factories 到 SpringIOC 的 bean (单步源码级分析)

本文通过debug的方式向大家展现如何一步步从SpringApplication的run方法到创建内置SpringIOC容器并创建出bean的所有过程,前置知识为对SpringIOC具有最基本的认识,知道BeanFactoryPostProcess的作用以及BeanDefi加粗样式nition的作用,当然要对JavaSE有基础的认知

话不多说,开始debug
在这里插入图片描述
F7直接进入
在这里插入图片描述
再次进入,但是这里要注意!要看SpringApplication的构造器
在这里插入图片描述
继续跟进查看父类构造器
在这里插入图片描述
注意看,这里构造器中的某一个方法,调用了getSpringFactoriesInstance(xxx.class)方法,见名知意,这个方法的作用是把所有JAR包下的spring.factories定义的文件转化为实例并返回,当然不可能实例化所有的类对象,只是有选择的实例化给定的class所代表的对象,毕竟真正的实例化所有对象要交给后面的内置springIOC容器来做吗
在这里插入图片描述
进入getSpringFactoriesInstance(xxx.class)方法,发现只有一个方法,继续跟进
在这里插入图片描述
注意看核心来了!这里调用了SpringFactoriesLoader的loadFactoryNames方法,大概能猜到这个方法是用来干什么了吧?根据传入的class对象,获取spring.factories下的指定的类的名称,直接class.getName()不行么?这里是不是很蠢?为了避免引入太多复杂的东西,大家知道这里还传入了classLoader类加载器作为参数就行了,它就是一个很普通的对象,继续进入loadFactoryNames(xxx,xxx):
在这里插入图片描述

来到真正的核心方法了!调用loadSpringFactories方法,发现只传入了一个classLoader参数,并没有传入type参数,说明这个方法是具有普遍性的,是我们要找的核心方法了,点进去!
在这里插入图片描述

进去看核心方法,发现使用ClassLoader中的原生文件加载器getResources(String path)加载所有JAR包目录下的META-INF/spring.factories文件,并遍历这些文件,然后放入cache的容器中,并返回result对象,这里有必要讲解一下为什么会有三个循环
在这里插入图片描述
看看这三个循环分别遍历了什么,打开spring.factories看看,发现有黄色的key和绿色的value(注:黄色的key是对value的一种分类,类似监听器的属性,如果两个在不同JAR包下的spring.factories文件的其中一个key相同,那么这个key下的value也就是全限定类名就会合并到一起,归属于同一个key(factoryClassName),在某些生命周期中可以直接通过key(factoryClassName)指定获取分组下的对应的多个value(factoryName),因此key大多是接口或者注解等标识性组件):
在这里插入图片描述
继续回来debug,你会发现cache存了这么个东西,补充一下result是直接new出来一个Spring-Boot自带的容器工具类,可以存放多个key且一个key对多个value的容器,这样就不需要自己new一个 HashMap<String,Set<String,String>> 那么麻烦了。这里直接退出方法,

你只需要记得这前面的所有步骤就是为了调用这个方法,读取所有spring.factories文件并把里面的内容放置到cache中备用即可

然后一直跳出方法…

在这里插入图片描述
跳出方法也带大家看,必须每一步debug都带着走,回到这个方法,后面的调用没用,继续跳出方法(可以看左下方的序号判断当前跳出了几个方法)
在这里插入图片描述
继续退出方法,这些方法与主题无关(注:顺带提一嘴上面只是拿到了所有spring.factories记录的类的String全限定类名,具体的对象要通过下面的createSpringInstances方法(就在蓝色条的下面)实例化,而且是单独的实例化(指定的factoryClassName下的factoryName),而不会把所有的factoryName一起实例化):
在这里插入图片描述
OK,SpringApplication对象已经构造完成,回到开始继续debug,F7进入run(args)方法
在这里插入图片描述
进入run(args)方法,前面的步骤大概就是创建一个IOC容器,并且配置环境,可以跳过,直接跳到refreshContext(context)容器的方法中:
在这里插入图片描述
继续进入
在这里插入图片描述
继续进入,终于调用了真正的refresh()方法,进入
在这里插入图片描述
咳咳,不好意思,这个才是真正的refresh()方法,进入
在这里插入图片描述
终于看见经典的13个方法了,是不是很熟悉,进入invokeBeanFactoryPostProcessors(beanFactory),这个方法是对实现了BeanFactoryPostProcess接口的所有class对象提前实例化并执行接口的方法
在这里插入图片描述
进入invokeBeanFactoryPostProcessors,继续进入
在这里插入图片描述
来到这个方法,继续往下走(其实是方法太大,展示不完全)
在这里插入图片描述
这里执行注解增强器,这个注解增强器是SpringIOC本身就内置的,不需要任何额外的设置,作用是通过注解的方式生成beanDefinition对象,毫无疑问这里就是本文题目的关键了,因为beanDefinition对象一旦生成,SpringIOC就可以根据这个对象直接实例化bean

在这里插入图片描述
进入这个方法,毫无悬念的把第二个参数registry传入第一个参数中的postProcessBeanDefinitionRegistry()方法,以实现对registry的增强(registry就是BeanFactory对象)
在这里插入图片描述

继续进入
在这里插入图片描述

这个类的作用就是通过扫描@Configuration注解把具有这个注解的beanDefinition做进一步的扫描处理,以确定内部到底需要注册哪些beanDefinition,而main类正好带有@SpringBootApplication注解,这个注解里又有@SpringBootConfiguration注解,最后@SpringBootConfiguration又有@Configuration注解,所以直接被注册为需要被进一步扫描的beanDefinition(PS:其实这里省略了一个至关重要的核心重点,就是这个main类的beanDefinition什么时候被注册进IOC容器的?还记得开头跳过的initialize(xxx)方法么?文章最后会说)
(这个图有一点错误,其实不止扫描@Configuration,@Import、@Component、@ImportResource、@ComponentScan也会一并扫描!)
在这里插入图片描述

继续往下走,终于到了最核心的方法,把main类的beanDefinition直接传入,本质上就是扫描mian类的@Configuration注解,进入
在这里插入图片描述
进入AnnotatedBeanDefinition分支,见名知意,这个是负责转化注解定义的beanDefinition,但是这个方法只会转化用普通注解如@Controller、@Server、@Reposity定义的Bean,不会转化@Import等特殊的配置类Bean,具体操作就是获取main类的相对路径,然后遍历相对路径下的所有文件,逐个扫描class文件并获取class对象然后把带有注解的class对象注册为beanDefinition注册到IOC容器中,与主题无关所以跳过
在这里插入图片描述

继续进入,重点方法来了!扫描@Import注解!
在这里插入图片描述

先不急着debug,看看mian类的注解@SpringBootApplication的结构到底是怎么样的
在这里插入图片描述
进入@EnableAutoConfiguration,发现了这个Import注解熟悉IOC的应该知道里面的class对象意味着什么
在这里插入图片描述
回到debug,进入process()方法,这里直接new了个Import解析器,并调用自己的register方法

然后调用processGroupImports()方法
在这里插入图片描述
顺便给你们看看handle::register方法做了什么,给groupings容器内放了个DeferedImportSelectorGrouping(xxx)对象
在这里插入图片描述
继续走最后一个方法,遍历上面注册的gourpings容器,拿到上面注册的对象,并且调用对象的getImportants()方法,跟进
在这里插入图片描述
调用创建这个对象时传入的group的process方法,并把注解信息、
在这里插入图片描述
跟进这个方法
在这里插入图片描述
继续进入
在这里插入图片描述
一直到这一步,终于和开头加载spring.factories并放入cache衔接上了!
在这里插入图片描述
看到需要匹配的类型是什么了吧,对照一下前面我强调过的紫色的cache结构HashMap<String,Set>和spring.factories的k-v是怎么匹配的,通过调用SpringFactoriesLoader.loadFactoryNames可以返回所有spring.factories下key为EnableAutoConfiguration的分割后的value
在这里插入图片描述
在这里插入图片描述
拿到bean全限定类名后,做去重和排除处理,其实就是用List的removeAll方法排除,下面还有个filter方法是用来过滤已经在spring-autoconfigure-metadata.properties定义好的值,其实还有一个作用是过滤@OnClassCondition,既class对象已经被加载到jvm的,大多情况下是过滤掉前面已经读取完\META-INF\spring-autoconfigure-metadata.properties(另一个spring-boot配置文件,也可以在里面定义bean)文件并把class对象加载到jvm中
在这里插入图片描述
退出上面的方法,来到这,注意这里的注解信息是main类上的注解信息,不是beanNames的
在这里插入图片描述
直接出栈
在这里插入图片描述
因为@Import注解注册的bean中,本身也有可能也是带有@Import的,所以需要把上一步方法获取的beanNames转化为class对象,然后扫描class对象的注解信息,如果还有@Import注解,则进入递归分支递归调用自己
在这里插入图片描述
跳出刚才 的方法
在这里插入图片描述

继续跳出
在这里插入图片描述

在这里插入图片描述
然后我不想讲了,因为拿到beanDefinition就可以去遍历实例化bean了,去别的地方学习bean的生命周期吧。。。

猜你喜欢

转载自blog.csdn.net/cxlswh/article/details/114157246