Spring中IOC源码解读

前言:

本人写帖是记录自己学习的一个过程,并且也在分享的自己的学习笔记,本人很想为代码开源化作出一份贡献,本人也是一位努力的新手,如帖子里面有错误的地方也请各位与本人积极商讨!


正文:

在正文开始之前,我觉得很有必要说一段自己目前对程序员的认识把。"内卷"这是21世纪形容程序员的一个专有名词。目前我接触程序员也有几个年头了,接触Java后端也是第二个年头了,目前21岁的我也是参与在内卷的一个环境。所以呢我很想表达一个观点那就是——“当代程序员不缺知识,缺得是思想”。想要在一个很内卷的环境下生存必不可少的是努力,可是现在当凭努力很难出类拔萃,要的是一个学习的效率加上合理的管理时间,不要一味的傻努力,最后可能是感动的自己。如何有效呢,可能是找到好的学习路线,可能是找到好的学习资源。可是我觉得是要培养一个编程思想思维。何谈的思想呢?我觉得通俗易懂的话来说就是认识一个东西出现的套路和规律把。好比如说大家都知道CPU它是要跑的比内存快,但是很多情况是CPU与内存交互,所以很多时候是内存在拖慢CPU的效率,所以现在的CPU出现了三级缓存,他把一些数据缓存到自己的缓存中来加快速度。在如今的后端项目中想要系统的QPS高你的缓存中间件不能少吧,一个框架中想要效率高你的缓存不能少吧,比如Spring的三级缓存,Mybatis的二级缓存。大家都是使用的缓存,而缓存的无非就是说把处理过的内容保留一份在自己这里,下次直接从自己这里获取到,不用再从他人之手获取到。我觉得这就是一种思想,思想存在于各个地方,不仅仅是编程中,大家的思想上来了,学一个知识还不容易么!


大家刚接触到Spring的时候,就听说啥"控制反转"IOC、面向切面AOP。那控制反转它到底干了些啥你可能还不懂,可能刚写Spring的时候还是使用XML形式来写一个.XML文件,然后写一堆东西,然后在main方法中创建ApplicationContext,再通过.getBean()方法获取到定义好的类,可能这就是你的Spring的"Hello world",可能听讲课的老师说什么底层是通过反射。那反射它又是一个啥东西呢?你可能也不怎么懂,懵懵懂懂的又开始了AOP,又说什么在不动原来方法的情况下添加一些内容啥啥啥的,前后顺序还可以通过注解来控制。当时你才有java的一些基础,面向对象也还是似懂非懂可能还不懂要他干嘛,new对象干嘛的,到这里怎么连new对象都没有了,xml配置文件啥啥啥的一大堆,可能都要怀疑人生,我不是来学编程的么,怎么就一些这东西,天天就是System.out.println("hello world");在控制台输出点东西,隔壁班都有图形化界面了(Swing)。可是时间不等你,后来又开始SpringMVC、Mybatis一大堆的框架朝你走来,你越来越怀疑人生了,没错这就是我一年前的经历,到现在我也是能看懂很多框架原理(这个懂也不是真得很懂把,其实你越学越会发现自己越来越无知,因为你们还有很多核心思想等着你来品!)。好了我的故事还有很多以后的帖子再来说。

回到今天的内容,Spring的IOC,控制反转顾名思义就是平常new对象都是给程序员来控制,Spring出现就是交给Spring来管理对象,你只需要表明一下,然后再需要的时候从Spring容器中获取,大大降低了程序之间的耦合度。那么今天就来讲解一下Spring中IOC的一个启动流程。废话不多说先上本人自绘的流程图基于XML形式的ClassPathXmlApplicationContext。

Spring中IOC源码解读_第1张图片 SpringIOC基于XML形式的流程图


“一个简单的容器管理居然能有这么多类这么复杂”,没错Spring的类真的很复杂,我觉得对于一个初学者确实很容易被这么多类起步就劝退的。但是没关系,这个帖子的教程是绝对仔细,尽可能让第一次接触Spring源码的同学也能轻易追完。在直接追源码的开始之前再把Bean的生命周期流程图过一遍吧,注意Bean的生命周期会出现在源码中,但是源码可不止这么点生命周期。

Spring中IOC源码解读_第2张图片

 好了那接下来就是debug断点追源码的环节了

Spring中IOC源码解读_第3张图片

Spring中IOC源码解读_第4张图片

Spring中IOC源码解读_第5张图片

Spring中IOC源码解读_第6张图片

 这里是准备好的代码,xml、实体类、前后置器实现类、测试类,我就不复制源码了,代码量特别的少,大家可以自己截图识别一下。

Spring中IOC源码解读_第7张图片

Spring中IOC源码解读_第8张图片

Spring中IOC源码解读_第9张图片

第一个重要类就是AbstractApplicationContext ,继承与ApplicationContext,而ApplicationContext继承于BeanFactory

注意:

这里也是一个面试题:请问BeanFactory和ApplicationContext的区别

答案我会在最后给出,因为整体追完,答案也大概就出来了!

我对他的感觉就好像SpringMVC的doDispatch()方法,核心的几个方法都在这里,然后方法套方法一直套娃的操作。没错,他确实就是这样的。如上图我把重点的方法都有标记,其他一些初始化的方法就不断点进入查看了,重要的方法如下: 

 ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();    
 this.registerBeanPostProcessors(beanFactory);
 this.onRefresh();
 this.finishBeanFactoryInitialization(beanFactory);

首先进入到obtainFreshBeanFactory();

Spring中IOC源码解读_第10张图片

Spring中IOC源码解读_第11张图片

 可以清晰的看出迎来了第二个类AbstractRefreshableApplicationContext,这里大致就是创建出内部的BeanFactory,这里使用的是BeanFactory的子类DefaultListableBeanFactor,也是一个特别重要的类,后面会对他做出介绍。然后就是执行loadBeanDefinitions()方法,从字面意思就可以得出他是加载BeanDefinition,这是Spring内部非常重要的一个类,他是用来存储xml解析后的数据的,所以这个方法就是对XML的解析并且把数据存入到BeanDefinitionMap中,下面我来证实一下吧!

Spring中IOC源码解读_第12张图片 接下来我就不往里面继续追了,从类名和方法名就可以得出是对XML做解析的,解析到的数据放入到Map beanDefinitionMap = new ConcurrentHashMap(256);

this.registerBeanPostProcessors(beanFactory);

那咱们回到AbstractApplicationContext中refresh()方法中,继续往下面重要的方法继续追。下个重要的就是,从方法名顾名思义就是注册BeanPostProcess的信息。咱们这里是有实现BeanPostProcessor接口,并且对前置和后置的方法都有重写,那么看看Spring 是怎么实现的。

Spring中IOC源码解读_第13张图片

Spring中IOC源码解读_第14张图片

 可以看出这个方法的实现的代码比较多,重要的方法我已经打好了断点了,并且可以看出这里有几个循环,所以他到底做了一些什么呢?

首先第一行的getBeanNamesForType()方法从BeanDefinition中获取到解析好的数据,从中获取到

BeanPostProcess的内容

 String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

接下来就是第一个For循环了, 这里大概就是在判断解析出来的数据是否实现Priorit-yOrdered接口,如果实现了Priorit-yOrdered接口就获取到Bean信息,并且添加到对应的集合中。如果没有实现就继续判断是否实现了Ordered接口,都没有实现就添加到nonOrderedPostProcessorNames集合中。接下来2个循环就是Ordered对应的集合和nonOrderedPostProcessorNames集合的一个迭代遍历了。

Ordered的介绍:

在Spring 的框架中大量使用到策略模式,很多类之间都互相有关系,而程序员想自己改变类的加载顺序就可以实现Ordered接口来进行改变。

举例:在AOP的使用中,如果有多个代理类,这时候就可以使用到@Order注解来控制优先级了,数值越低优先级越高!

        for(int var10 = 0; var10 < var9; ++var10) {
            ppName = var8[var10];
            if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
                pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
                priorityOrderedPostProcessors.add(pp);
                if (pp instanceof MergedBeanDefinitionPostProcessor) {
                    internalPostProcessors.add(pp);
                }
            } else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
                orderedPostProcessorNames.add(ppName);
            } else {
                nonOrderedPostProcessorNames.add(ppName);
            }
        }

由于我Dome中并没有并实现以上的两个接口,所以肯定是添加到nonOrderedPostProcessorNames这个集合的,那么就看到对它的遍历过程。

Spring中IOC源码解读_第15张图片

可以看到我的第一个断点在getBean()这个BeanFactory接口定义的方法,干嘛的顾名思义就是获取到Bean信息,那么我们看看他的实现把,继续往下追。

 pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);

 可以看到这里是套了一层娃,那么在Spring系列框架源码中一般do开头的方法就是真正的执行方法,那么doGetBean()是否是执行方法呢?咱们继续看

由于doGetBean()的方法类容实在太多太多了,我就不截图展示, 我把他的重要思想,和重要的方法给截图截下来。

Spring中IOC源码解读_第16张图片

this.getSingleton(beanName);

首先介绍一下这个方法的作用,就是尝试从缓存中获取到之间存入的Bean实例,这也就是我开头讲的一个思想,框架中使用到缓存来提高效率,并且缓存在这里有一个很重要的意义

大家有没有想过,在Spring中,A类中引用了B类,B类中又引用了A,那他们在实例化过程中是不是就是一个死循环了?

没错这就是大家已经熟知了循环依赖问题,也是一个很常见的面试题了。

Spring如今这么火的一个框架,那他底层是如何去解决这个问题的呢?

这里我大概的提一嘴把,Spring使用缓存不紧紧是为了提高效率,这里的缓冲可以解决循环依赖的问题,使用到三级缓存,因为在Spring的Bean的生命周期中,内部是先实例化再赋值,而实例化后就添加到三级缓存中。就拿上面列子来讲把,当A实例化后添加到三级缓存中,当他给属性赋值的时候,发现引用了B类,那么就会开始实例化B类,当B类实例化后发现又引用了A类,此时A类已经实例化完毕了,缓存在三级缓存中,此时B类就可以获取到A类,然后把A类放入到二级缓存中,然后B把整个创建流程走完,接着A再把它其他的流程走完,最后A和B类都会放入到一级缓存中。

咱们接着说getSingleton()方法,此方法是尝试获取到缓存中的值,而我的前置后置BeanPostProcessor的实现类并没有创建到SpringIOC中,怎么可能会有值呢?

doGetBean()这个方法中, 代码量从118-255行,你要一行一行的看岂不是累死,在他的第一行就是一个尝试从缓存中获取到数据,从第一行就能明白他是一个缓存思想,这时候这个方法的一个大概流程不就出来了。

好比大家应该都使用过Redis

它在你们项目中都是用来做缓存,当缓存中没有你就查询DB,查询完DB就放入到Redis缓存中,没错这就是一种缓存思想。

在这里也是这样的。

既然没有缓存 ,那么肯定是会去创建Bean,接着去看创建Bean的代码

Spring中IOC源码解读_第17张图片

 在这段代码中,我一共打了2个断点,第一个断点是获取到XML解析的信息,第二个断点是创建Bean的方法了,可以看到使用的是一个lambda表达式,这也是java8的新特性。不过java8确实已经8年了,不过java8确实是一个很稳定的版本。

lambda表达式其实就是一个匿名内部类的一个实现。

Spring中IOC源码解读_第18张图片

 我们查看到getSingleton()方法,因为使用的是lambda表达式,所以又要跳转回来,这个方法的实现类就是存三级缓存的类DefaultSingletonBeanRegistry。

所以进入到createBean()这个方法,这个方法的执行体肯定就是创建Bean的一个具体过程了。

Spring中IOC源码解读_第19张图片

 doCreateBean()方法就是执行方法,由于方法也是特别的长,我就讲重要的方法。

//反射执行实例化的方法
instanceWrapper = this.createBeanInstance(beanName, mbd, args);

createBeanInstance()通过反射实例化Bean的方法

this.addSingletonFactory(beanName, () -> {
     return this.getEarlyBeanReference(beanName, mbd, bean);
});

在讲addSingletonFactory()方法之前,我们回忆一下之前讲三级缓存的时候,是不是说Bean实例化以后,在没有赋值之前是放入到三级缓存中的,所以这个方法就是添加到三级缓存中。接下来我就证明一下自己把。

Spring中IOC源码解读_第20张图片

Spring中IOC源码解读_第21张图片

已经把实例化的Bean放入到三级缓存中,并且三级缓存的HashMap的key,value是

Map>,也就是value并不是存放的Bean实例,存放的是工厂信息

Spring中IOC源码解读_第22张图片

 根据我的认识,首先三级缓存的出现是为了解决循环依赖的问题,但是又不想太浪费性能,所以三级缓存中就存放一个简单的工厂信息。

继续把doCreateBean的其他重要方法来介绍

this.populateBean(beanName, mbd, instanceWrapper);

这就是给Bean赋值的方法,不过细追。

exposedObject = this.initializeBean(beanName, exposedObject, mbd);

这个方法执行的内容还是挺多的,比如Aware接口的实现,BeanPostProcessor前后置处理,实现InitializingBean接口的一个处理。就不过细追。

this.registerDisposableBeanIfNecessary(beanName, bean, mbd);

这个方法就是查看你是否实现了DisposableBean接口,如果实现就把信息注册,方便后面的流程使用。

DisposableBean的介绍

在Bean摧毁之前再完成您定义的内容。也是一个Bean的生命周期的一个流程。

然后整个创建工作就完成了,这只是doCreateBean()方法执行结束,其实并不是全部执行结束了,因为套娃,你要返回去执行其他方法,好比说就是一个递归执行,把方法全部压进栈中,好了这些就不多说。在前面说了缓存在一个Bean全部创建好以后会放入到一级缓存中,那么接下来的工作肯定就是放入到一级缓存中。

Spring中IOC源码解读_第23张图片Spring中IOC源码解读_第24张图片

  不出意外我们找到了添加到一级缓存的源码处!

等等,回来最开始,我们是不是在追IOC的启动流程,那肯定是在创建Bean,那怎么我刚刚追了这么多方法才是registerBeanPostProcessors()的方法。其实我最开始也是很蒙的,为什么注册

BeanPostProcessor还要实例化Bean之类的,在我迷茫的时候,我才发现我XML里面有配置

Spring中IOC源码解读_第25张图片

然后我把这几行XML给删除再执行发现是没有影响任何业务逻辑的。

Spring中IOC源码解读_第26张图片

 为什么呢?因为BeanPostProcessor的执行方法通过CopyOnWriteArrayList绑定信息,是在Bean实例化赋值再去执行的。

所以呢,有认真一起追的朋友到现在就发现,Bean的创建流程我也已经追完了。因为我在XML中配置了后置器的Bean前面已经实例化赋值完毕了。但是真正要注入到IOC容易的类还没有添加进去,那我下面我们还是执行完毕把。

继续回到AbstractApplicationContext中的refresh()方法

Spring中IOC源码解读_第27张图片

前面两个断点都执行完了,并且误打误撞其实把第三个方法的执行流程全部执行完毕了....但是我们还是走一遍流程把全部执行完毕把,那么下面我就不仔细讲解了。

Spring中IOC源码解读_第28张图片

Spring中IOC源码解读_第29张图片

Spring中IOC源码解读_第30张图片

这里也就是获取到解析XML的数据然后遍历,然后查看是否有缓存,没有缓存就执行getBean()这个方法。那么我还是继续追进去把

Spring中IOC源码解读_第31张图片

Spring中IOC源码解读_第32张图片

没错又来到这里了,我这里就不一一仔细的讲解了,老规矩查缓存没得就执行那个带lambda的方法区创建Bean,这里肯定是没得缓存的,所以就直接进入到createBean方法再看一遍执行顺序。其实也还好巩固一下创建先后顺序和创建是用的那个方法!

Spring中IOC源码解读_第33张图片

反射创建Bean,也就是实例化Bean

 添加到三级缓存。

Spring中IOC源码解读_第34张图片

给Bean赋值

Spring中IOC源码解读_第35张图片 Aware接口、BeanPostProcessor前后置、InitializingBean接口、init-method方法,先后顺序可以从我的控制台查看,不过我最后会对他们的先后顺序做一个总结。

Spring中IOC源码解读_第36张图片

Bean实例化全部执行完毕后就会把三级缓存删掉并且添加到一级缓存中的步骤了老规矩上截图证明一下,而且一级缓存的value是存放的bean,而三级缓存是存放的工厂信息。

所以这里可以总结一下三级缓存都存了些啥把

一级缓存:创建好的bean对象实例

二级缓存:我这里并没有产生循环依赖,所以并没有使用到二级缓存,但是里面是存放的是没有赋值但是实例化的bean实例

三级缓存:存放的是bean的工厂信息

 好了整个流程都执行完毕了,虽然里面还有一些接口我的dome中没有实现,但是流程我都是有全部追完,关于一个bean的生命周期可以看最上面的流程图。


总结:

不得不说Spring源码是相当复杂的,很多类的一个来回跳转,其实最开始我也是被他的类名给小劝退的,看上去全部的名字都差不多,其实实际上他们这些类之间多多少少都是有一些关系的,这就是策略模式。Spring虽然功能上就容器对象管理、AOP切面,但是他给程序员暴露出去很多接口方便对Spring做一个扩展,这就是面向接口变成的一个魅力把。

你可能感兴趣的:(源码解读,源码分析,spring系列,spring,java,后端,intellij,idea)