Spring源码解析(1):Bean容器
这一篇开始,正式进入Spring源码解析。本系列主要讨论单例Bean。
需要的知识储备有:
源码之所以难,是因为体量庞大、抽象层次深。如果之前从来没看过,很难有全局观。本系列采用先局部,再整体,再局部的方式展现Spring的源码。
今天这篇不看整体,只看局部:存bean的地方。
主要内容:
- 逃离复杂的组织
- 快说,你把东西存哪里了!!
- ApplicationContext与BeanFactory
逃离复杂的组织
网上很多Spring的文章开头必然先甩出一张继承体系图(没有任何预热):

个人觉得,这种做法除了把小白绕晕,没有任何好处。就好比谈恋爱多年第一次去女方家,你突然拿出一本家谱给我,我哪里知道谁是谁。
我想先把Spring拍扁了给大家看看,等大家大概熟悉以后,我再想着把它捏圆了。
如何拍扁?
任何继承或实现的方法和字段,我都当成是子类自己定义的,暂时不去关心什么父类、接口。
比如:
class Animal
public class Animal {
// 有名字
public String name;
// 会吃东西
public void eat(){}
}interface Art
public interface Art {
// 画画
public void paint();
// 唱歌
public void sing();
}class Human extends Animal implements Art
public class Human extends Animal implements Art{
// 有生日
public Date birthday;
// 会画画
public void paint() {
}
// 会唱歌
public void sing() {
}
}class Student extends Human
public class Student extends Human {
// 考试排名
public String rank;
// 会写作文
public void write(){}
}我就写了这么简单的两次继承,估计已经有部分同学晕了。怎么理清类与类之间的关系?
当然,你确实可以从继承体系切入,比如:

一目了然是吧?
但是有几个问题:
- 图上看不到具体的方法和字段
- 继承结构越复杂,这种图能提现的信息越少,到最后反而成了一种记忆负担
老实说,这种继承体系图只适合复习,而不是预习。
我并非刻意贬低继承体系图,但必须承认任何工具都有它的局限性,尤其当它面对的是Spring这样的怪物时。
来看看我的做法:
这才是真正的一目了然
当然,由于eat()方法实际并不由Student定义,实际调用时可能是这样:

但我只要知道student.eat()能返回执行结果就行,其他的我不管。
听到这,突然有个聪明的读者跳出来说:
一般来讲,方法都是public的,子类调用父类方法没问题。但是字段很多时候都是private的,你这样处理就有问题了。
那我倒要问问各位,你们平时定义了一个字段,会直接使用点语法访问吗:
Student s = new Student();
// 直接通过点语法访问,得到学生考试排名
System.out.println(s.rank);点语法访问的前提是,这个字段是public的,否则只能通过方法访问:getter/setter方法或者普通方法。而通过方法访问字段才是正道,点语法那是走后门,不一定通用!
举个例子,我在Human类中定义private String text字段,并提供了public void getText()方法。
public class Human extends Animal implements Art{
/*省略其他方法和字段*/
// 测试数据
private String text = "Human private message";
// 得到测试数据
public void getText(){
System.out.println(text);
}
}测试
public class Test {
public static void main(String[] args) {
Student s = new Student();
s.getText(); // 测试结果是正常输出:Human private message
}也就是说,虽然父类的字段是private的,但如果子类继承了父类方法,且这个方法中刚好用到了这个字段,那么就相当于间接访问了。
这就是我早期分析Spring的思路:忘掉复杂的继承体系,把Spring拍扁,浓缩到只有两个对象(暂时不知道没关系)
- AnnotationConfigApplicationContext
- DefaultListableBeanFactory
所有继承的、实现的、乱七八糟的东西,我都当成是直接定义在它俩内部的。
最后,让大家看看什么叫英雄所见略同(IDEA)
以前我没得选,现在我想简简单单做个BeanFactory
快说,你把东西存哪里了!!
Spring作为IOC容器,首要任务自然是解决对象存储问题。长久以来,我们都认为Spring是个能帮我们完成自动注入的大Map(不知为何,有点萌),但这其实是对它的误解。
说到Spring的源码,无论是谁都要扯扯ApplicationContext和BeanFactory。我也不能免俗,就从这里开始吧。
首先,这两个都是接口,而且ApplicationContext继承了BeanFactory。

所以,我们先来看看BeanFactory:
没看到addBean()之类的方法
不是说BeanFactory是Bean工厂吗,怎么“只出不进”?
那我又要反问在座各位一句了:你平时写代码,用什么存数据呀?
用接口吗?接口哪能存东西啊!!
所有和存储相关的,追究到最后必然是字段或者集合。
字段能存东西,是因为成员变量和成员方法是对象的基本组成,对象通过成员变量组织数据关系。集合也能存东西,因为它本身就是Java糅合了数据结构造出来专门存数据的。而且通常来说,相较于String、Integer这些,我们更愿意用集合作为一个成员变量,大气!
public class Student {
// 成员变量,用List存朋友
private List<String> myFriends = new ArrayList<String>();
// 其他方法...
}而BeanFactory接口内部并没有定义字段去存储bean,仅仅是提供了方法规范:
所有实现BeanFactory接口的子类必须实现getBean()方法。
就好比有人提供了一张工厂设计图,规定了以后造的厂房必须有1个门、2个窗户,但设计图本身并不能存东西。既然BeanFactory接口本身不存储bean,那么不提供addBean()之类的方法也说得过去。
那真正存bean的工厂在哪呢?
向大家隆重介绍一下DefaultSingletonBeanRegistry(省略部分字段、方法)
DefaultSingletonBeanRegistry是一个类。直译的话,就是“默认的单例bean注册表”
也就是说,DefaultSingletonBeanRegistry是专门来管理单例bean的。那它是怎么做的呢?主要从两个方面考察:
- 容器在哪?
- 如何存取?
容器在哪?
DefaultSingletonBeanRegistry最重要的三个成员变量:
- singletonObjects(存放单例bean)
- earlySingletonObjects
- singletonFactories
二、三级缓存暂时不用理会,只关注singletonObjects即可
我们之前所理解的Spring容器非常狭隘,认为它就是一个Map。但现在我们知道了,真正存bean的其实是一个叫singletonObjects的Map,但singletonObjects对于整个Spring体系来讲,九牛一毛。甚至DefaultSingletonBeanRegistry本身也只是在Spring容器的一个小角落。

还有一个问题值得关注:既然是专门管理单例bean的工厂,它如何保证单例?
concurrentHashMap转成set
原来,DefaultSingletonBeanRegistry搞了一个Set<String> singletonsCurrentlyInCreation,专门来存放正在创建的单例bean的名字(注意,只是名字而不是bean,因为bean还在创建中)。
除了singletonsCurrentlyInCreation这个字段,它还设计了两个方法:
beforeSingletonCreation(String beanName)
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}afterSingletonCreation(String beanName)
protected void afterSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.remove(beanName)) {
throw new IllegalStateException("Singleton '" + beanName + "' isn't currently in creation");
}
}控制单例示意图
一个单例bean在创建前,先往singletonsCurrentlyInCreation存自己的name,其他bean在创建时,会先来这里确认有无同名bean
如何存取?
DefaultSingletonBeanRegistry提供了存取bean的一系列方法
我相信,看到这大家都没发现一个问题:DefaultSingletonBeanRegistry没有getBean()方法,因为它压根就没实现BeanFactory!!
图中左侧都是和bean别名相关的,不是很重要
它实现的是SingletonBeanRegistry,专门管理单例bean的。

好了,来捋一捋。目前为止,故事是这样的:
我们本来打算看看Spring的两大顶级接口ApplicationContext和BeanFactory,结果看BeanFactory时,发现它根本不是一个具体的工厂,不提供容器(没有可以存bean的成员变量),只规定了getBean()方法。于是,我们翻了翻,在Spring源码的某个角落发现了DefaultSingletonBeanRegistry,它是一个类,还设计了几个Map作为成员变量专门存放单例bean,也就是我们常说的“单例池”。但是,当我们研究DefaultSingletonBeanRegistry是如何存取东西时,发现它并没有实现BeanFactory!
于是,现在出现了一个矛盾!
我们已经基本确定单例bean就是存在DefaultSingletonBeanRegistry中,但是它却没有实现BeanFactory,没有提供getBean()。那么AnnotationConfigApplicationContext#getBean()最终通向何处?
(这句话一部分读者可能看不懂,就是说,我们原以为AnnotationConfigApplicationContext的getBean()会直通Bean容器DefaultSingletonBeanRegistry的getBean(),最终取到bean实例)

爱因斯坦曾说过,搞物理首先最重要的是想象力,要敢于大胆猜想。于是我们猜测,AnnotationConfigApplicationContext#getBean()不论怎么绕,最终都会来访问DefaultSingletonBeanRegistry的单例池earlySingletonObjects:
earlySingletonObjects是DefaultSingletonBeanRegistry定义的一个字段,俗称单例池,用来存储单例bean
我们在开头说过,一个字段需要通过方法才能访问。我们找了下,发现DefaultSingletonBeanRegistry有好几个getSingleton()方法:

让我们debug试试吧。
第一步,给applicationContext.getBean()打断点并开始运行:

第二步,找到DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)打上点断,然后放开上面的断点,让程序停在getSingleton(...)方法:
从单例池earlySingletonObjects中获得person
整体图


我们发现,AnnotationConfigApplicationContext#getBean()方法,经过层层调用最终还是找到了DefaultSingletonBeanRegistry的earlySingletonObjects。
按我的猜想,必然是AnnotationConfigApplicationContext通过层层继承,最终继承了DefaultSingletonBeanRegistry,然后getBean()方法在某一处调用了getSingleton(),最终从earlySingletonObjects拿到bean。

查看AnnotationConfigApplicationContext的类继承图应该可以看到它继承了DefaultSingletonBeanRegistry:
不用找了,图中根本没有DefaultSingletonBeanRegistry
我去,AnnotationConfigApplicationContext和DefaultSingletonBeanRegistry竟然没有任何关系,这两个类互相不认识...一瞬间,线索全断了。毫无关系的两个类,是如何调用到的?
冷静...冷静。
仔细想了想,按我们最初的设想,这单例池最好应该直接放在AnnotationConfigApplicationContext中,但现在发现其实人家单例池是在DefaultSingletonBeanRegistry中。这是已成事实的东西,没法改变。我们要做的就是克服客观上的困难,让AnnotationConfigApplicationContext去DefaultSingletonBeanRegistry访问单例池中的bean。
除了继承后使用父类方法访问父类变量,还有别的途径吗?
有,使用组合/聚合!
什么是组合/聚合?就是把整个对象直接塞到另一个对象中!当然,实际上应该是对象的引用,比如这样:
Parent
public class Parent {
// Parent包含Student
private Student student;
// 查看成绩
public void getScore(){
// 内部调用student.getScore()得到成绩
System.out.println(student.getScore());
}
}Student
public class Student {
// 成绩
private String score;
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
}像上面这种情况,parent.getScore()虽然能得到学生的成绩,但是Parent本身和Student没有继承或实现关系,所以你在Parent的继承图上看不到Student。
那么,Spring的AnnotationConfigApplicationContext和DefaultSingletonBeanRegistry也是这种情况吗?
直接点进applicationContext.getBean()

我们来到了AbstractApplicationContext
翻译:ApplicationContext接口的抽象实现类,只是简单地实现了普通的上下文功能,使用模板方法模式,具体方法实现留给子类
请注意getBean()方法上面的注释:Implementation of BeanFactory interface,意味着这些getBean()方法是对BeanFactory的实现
卧槽,这注释写得也太人性化了吧,生怕你不知道AbstractApplicationContext实现了BeanFactory
所以AnnotationConfigApplicationContext也算是间接实现了BeanFactory
AbstractApplicationContext#getBean()(既然实现了BeanFactory,就有getBean()方法)
@Override
public Object getBean(String name) throws BeansException {
assertBeanFactoryActive();
// 果然不出我们所料,AbstractApplicationContext内部好像组合了一个BeanFactory
return getBeanFactory().getBean(name);
}查看AbstractApplicationContext#getBeanFactory(),看看具体组合了哪个BeanFactory
结果发现这里用了模板方法模式,具体的实现留给了子类,AbstractApplicationContext本身并没有组合BeanFactory
继续点下去,我们来到了AbstractApplicationContext的子类GenericApplicationContext
GenericApplicationContext说自己包含了一个BeanFactory
GenericApplicationContext#getBeanFactory()

查看GenericApplicationContext的字段,我们终于发现了一个BeanFactory的子类:

所以,AnnotationConfigApplicationContext#getBean()整个过程应该是这样的:
利用模板方法模式,把getBeanFactory()留给子类实现,最终GenericApplicationContext组合了一个DefaultListableBeanFactory
至此,之前种种矛盾都由DefaultListableBeanFactory解决了:
【测试代码得到的结论】
ApplicationContext继承了BeanFactory,AnnotationConfigApplicationContext是ApplicationContext子类,通过getBean()得到了单例对象。【观察源码得到的结论】
DefaultSingletonBeanRegistry拥有单例池earlySingletonObjects,但它没有实现BeanFactory,没有getBean()方法对外界访问。【矛盾】
那么AnnotationConfigApplicationContext#getBean()怎么访问earlySingletonObjects?
答案就在DefaultListableBeanFactory这个“中介”上:

DefaultListableBeanFactory是ApplicationContext与SingletonBeanRegistry的中介
小结:

ApplicationContext与BeanFactory
按照上面的分析,ApplicationContext继承了BeanFactory,好像和BeanFactory没啥区别。其实不是的。有了上面的预热,我们再来重新审视文章开头那张继承体系图:

也就是说,除了BeanFactory,ApplicationContext还继承了很多乱七八糟的接口来扩展自己的功能,比如:
- ResourcePatternResolver接口,继承自ResourceLoader,我们常说的包扫描就是它负责的
- ApplicationEventPublisher接口,关系着Spring的监听机制
- EnvironmentCapable接口,提供了获取环境变量的方法。环境变量意味着什么?关系着@PropertySource、@Value、@Profile等注解
- ...
来看一下官方文档对ApplicationContext的解释:
The
org.springframework.beans和org.springframework.context包是Spring Framework的IoC容器的基础。BeanFactory接口提供了能够管理任何类型对象的高级配置机制。ApplicationContext是BeanFactory的子接口,扩展了以下几点:更容易与Spring的AOP特性集成
消息资源处理(用于国际化)
事件发布
应用程序层特定的上下文,如web应用程序中使用的WebApplicationContext。简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。ApplicationContext是BeanFactory的一个完整超集。

本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。