Spring底层原理
前言
离上一篇文章记录差不多过了半年左右的时间吧,这段时间很多面试准备,知识点的学习基本被我转移到了书面笔记上,秋招仍在继续,你我怎能懈怠!特捡起上半年的老本,继续自己的CSDN博客之途。
看过我之前写的文章的小伙伴肯定都有一个感受,啊写的好基础呀!没错是的,之前无论是技术知识的积累还是知识层面的广度和深度,都近似一个小白在写杂谈…
这段很长时间的离开希望能让以后我的分享提升一个档次,至于为什么要坚持写博客?
- 互联网时代软件行业技术开源精神的普及,分享技术是一件有助于自己,更帮助其他人从而达到共赢的很棒的事情。
- 帮助自己巩固记忆知识点,好记性不如烂笔头,同时费曼学习法教会我们如果能把别人讲懂,说明我们已经熟练掌握了该知识点。
- 自己喜欢阅读和写作这两件事,将自己想要表达的观点和看法写在文章里,是一件很有成就感的事情。不管有没有人阅读,负面评论什么的,自己从中可以获取到乐趣。
目标
由于仍处于秋招白热化期间,我可能有时沉浸在自己面经的复习中,但我计划至少两天写一篇原创文章。希望可以完成!
正文
我们知道Spring是Jave EE里面划时代的轻量级框架设计,代替了之前重量级的EJB技术(Enterprise JavaBean),可以解决对象依赖问题,降低耦合性,同时提供声明式事务管理功能。
Spring两个核心功能为 控制反转(IoC)和提供面向切面编程(AOP)。
今天我们就来了解下Spring的核心IoC容器,它是怎么导入bean并进行实例化对象操作的?
导入Bean
第一个问题我们首先要知道我们通过哪几种方式可以为IoC容器导入bean?
-
xml方式
xml作为最基本的配置方式,其实是比较重量级比较繁琐的一种方式,我们在xml文件中声明一个个的 <bean标签,指定bean的id和要导入的全类名。 -
Java类配置
使用Java类定义Spring配置。使用@Configuration注解需要作为配置的类。(表示该类将定义Bean)@Configuration public class ApplicationContextConfig{ @Bean public String message() { return "hello"; } }
-
开发中我们用的最多的是组件注解,@Component, 常见的有@Controller, @Repository, @Service。
BeanDefinition
上述几种方式可以为我们的IoC容器导入bean,那么问题来了,我们导入IoC容器中的bean 被 Spring 识别成什么了呢,怎么进行实例化的呢?
我们知道我们可以通过容器提供的 getBean( )方法获取到对应id的bean对象,但是在 getBean() 之前做了哪些操作呢?
我们就要了解一下BeanDefinition这个对象了,我们被注解标记的类被JVM类加载器加载到内存中形成对应的BeanDedifinition的bean定义对象,存储在一个map集合(BeanDefinitionMap)中。如下图:
相信很多朋友都没听说过Spring里面的这个bean定义对象,没关系,我们学习新知识都是从一开始什么都不知道到了解概念再到熟练,精通。
单单BeanDefinition可不能直接实例化,里面需要涉及到一个接口,BeanFactoryPostProcessor,我们可以把它理解为在实例化对象之前可以修改我们的bean定义对象即BeanDedinition对象,这个接口中的一个实现类BeanDefinitionRegistryPostProcessor可以完成类似“设计小组”的功能,对字节码进行解析,这个接口的另外一个实现类可以实现类似“审核小组”的功能,用于修改 bean定义对象。
听到这里可能很多小伙伴已经云里雾里了,“这什么乱七八糟的,你的文笔就这,就这??”
别急,我可以拿人给大家打个不恰当的比喻,一个已经实例化的bean对象可以看成是一个已经18岁的成年人了。
这个BeanDefinition我们可以把它看作我们人最初的形态——受精卵,我们以后长成人的基因已经在这里确定了,那上面所说的BeanFactoryPostProcessor可以对bean定义对象作修改的嘛,没事啊,我们受精卵也可以作修改啊,不是有个“人类基因组计划”嘛,基因工程可以改变bean定义对象啊。之后getBean() 获取到实例对象,此时我们知道还有一个BeanPostProcessor接口(注意这个和上面的接口不一样!),这个就相当于我们现在流行的整容变性啊,之后真正成长成我们想要的样子了。(说多了都是泪啊!)
这就是导入到IoC容器后bean被Spring识别的过程。
实例化Bean
上面我们知道经过bean的定义解析过程,我们需要调用 getBean() 来实例化对象了,IoC容器是怎么一步步实例化对象的呢?
我们可以通过源码打断点跟随着getBean()的调用栈一步步地分析~
总体调用栈为如下所述:
getBean() ----> doGetBean() ----->getSingleton() ----->createBean() ------>doCreateBean(),到这里我们停一下,这一步就是真正的创建我们的bean实例对象的过程。
通过查看源码我们知道它调用createBeanInstance()方法通过反射来创建对象(早期对象),注意此时对象只是一个空壳对象,并不能直接构成我们最后的单例对象(Spring的IoC容器默认是单例的)。这里我们不得不简单科普一下Spring中的三级缓存了,不然咱们无法继续下去。
Spring三级缓存
一级缓存:就是大名鼎鼎的单例缓存池,用于保存所有的单实例 bean。
singletonObjects = new ConCurrentHashMap<>();
终于看到ConCurrentHashMap的实际应用场景了hh。
三级缓存:就是在createBeanInstance() 方法后创建的早期对象存放的地方。
singletonFactories = new HashMap<>();
我们总结:
- 所有的对象创建的过程中,都会去把自己的早期对象暴露到三级缓存中去。
- 针对代理对象的时候,从三级缓存到二级缓存会经过一次代理,创建完毕后,就会删除2级缓存,把单例对象存储到单例缓存池中。
紧接着上述创建早期对象,我们通过addSingletonFactory() 将早期对象暴露到三级缓存中,再调用populateBean() 设置早期对象的属性,如果发现其中依赖其他bean,则对其他bean执行同样的步骤,如果出现循环依赖的问题(即A依赖B,B依赖A),Spring的三级缓存可帮我们解决这个问题,我们在开发中并没有注意到这个问题。
以上就是今天总结分享的Spring导入bean的三种方式以及导入后bean的生命周期,由于博主也是没有工作经验的校招程序员,所以有很多地方说的不是很清楚甚至会引起误解,大家可以提出来我一定会悉心听取意见。
如果喜欢我的文章可以点点赞关注我,我也计划至少两天一篇原创文章。如果你能看到这里说明你肯定是一名热爱学习的人,真诚希望我们能为了自己的目标或者梦想不断努力,不断学习,希望大家都能得到自己想要的! 我是 promise,一名喜欢思考人生,喜欢讲大道理的程序员。