这一篇开始,正式进入Spring源码解析。本系列主要讨论单例Bean。

需要的知识储备有:

如何阅读Spring源码

Spring基础(1):两个概念

Spring基础(2):放弃XML,走向注解

Spring基础(3):复习

尚硅谷Spring注解开发视频_雷丰阳老师

源码之所以难,是因为体量庞大、抽象层次深。如果之前从来没看过,很难有全局观。本系列采用先局部,再整体,再局部的方式展现Spring的源码。

今天这篇不看整体,只看局部:存bean的地方。

主要内容:

  • 逃离复杂的组织
  • 快说,你把东西存哪里了!!
  • ApplicationContext与BeanFactory

逃离复杂的组织

网上很多Spring的文章开头必然先甩出一张继承体系图(没有任何预热):

img

个人觉得,这种做法除了把小白绕晕,没有任何好处。就好比谈恋爱多年第一次去女方家,你突然拿出一本家谱给我,我哪里知道谁是谁。

我想先把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(){}

}

我就写了这么简单的两次继承,估计已经有部分同学晕了。怎么理清类与类之间的关系?

当然,你确实可以从继承体系切入,比如:

img

一目了然是吧?

但是有几个问题:

  • 图上看不到具体的方法和字段
  • 继承结构越复杂,这种图能提现的信息越少,到最后反而成了一种记忆负担

老实说,这种继承体系图只适合复习,而不是预习。

我并非刻意贬低继承体系图,但必须承认任何工具都有它的局限性,尤其当它面对的是Spring这样的怪物时。

来看看我的做法:

img这才是真正的一目了然

当然,由于eat()方法实际并不由Student定义,实际调用时可能是这样:

img

但我只要知道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)

img以前我没得选,现在我想简简单单做个BeanFactory


快说,你把东西存哪里了!!

Spring作为IOC容器,首要任务自然是解决对象存储问题。长久以来,我们都认为Spring是个能帮我们完成自动注入的大Map(不知为何,有点萌),但这其实是对它的误解。

说到Spring的源码,无论是谁都要扯扯ApplicationContext和BeanFactory。我也不能免俗,就从这里开始吧。

首先,这两个都是接口,而且ApplicationContext继承了BeanFactory。

img

所以,我们先来看看BeanFactory:

img没看到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

img二、三级缓存暂时不用理会,只关注singletonObjects即可

我们之前所理解的Spring容器非常狭隘,认为它就是一个Map。但现在我们知道了,真正存bean的其实是一个叫singletonObjects的Map,但singletonObjects对于整个Spring体系来讲,九牛一毛。甚至DefaultSingletonBeanRegistry本身也只是在Spring容器的一个小角落。

img

还有一个问题值得关注:既然是专门管理单例bean的工厂,它如何保证单例?

imgconcurrentHashMap转成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");
    }
}

控制单例示意图

img一个单例bean在创建前,先往singletonsCurrentlyInCreation存自己的name,其他bean在创建时,会先来这里确认有无同名bean

如何存取?

imgDefaultSingletonBeanRegistry提供了存取bean的一系列方法

我相信,看到这大家都没发现一个问题:DefaultSingletonBeanRegistry没有getBean()方法,因为它压根就没实现BeanFactory!!

img图中左侧都是和bean别名相关的,不是很重要

它实现的是SingletonBeanRegistry,专门管理单例bean的。

img

好了,来捋一捋。目前为止,故事是这样的:

我们本来打算看看Spring的两大顶级接口ApplicationContext和BeanFactory,结果看BeanFactory时,发现它根本不是一个具体的工厂,不提供容器(没有可以存bean的成员变量),只规定了getBean()方法。于是,我们翻了翻,在Spring源码的某个角落发现了DefaultSingletonBeanRegistry,它是一个,还设计了几个Map作为成员变量专门存放单例bean,也就是我们常说的“单例池”。但是,当我们研究DefaultSingletonBeanRegistry是如何存取东西时,发现它并没有实现BeanFactory!

于是,现在出现了一个矛盾!

我们已经基本确定单例bean就是存在DefaultSingletonBeanRegistry中,但是它却没有实现BeanFactory,没有提供getBean()。那么AnnotationConfigApplicationContext#getBean()最终通向何处?

(这句话一部分读者可能看不懂,就是说,我们原以为AnnotationConfigApplicationContext的getBean()会直通Bean容器DefaultSingletonBeanRegistry的getBean(),最终取到bean实例)

img

爱因斯坦曾说过,搞物理首先最重要的是想象力,要敢于大胆猜想。于是我们猜测,AnnotationConfigApplicationContext#getBean()不论怎么绕,最终都会来访问DefaultSingletonBeanRegistry的单例池earlySingletonObjects:

imgearlySingletonObjects是DefaultSingletonBeanRegistry定义的一个字段,俗称单例池,用来存储单例bean

我们在开头说过,一个字段需要通过方法才能访问。我们找了下,发现DefaultSingletonBeanRegistry有好几个getSingleton()方法:

img

让我们debug试试吧。

第一步,给applicationContext.getBean()打断点并开始运行:

img

第二步,找到DefaultSingletonBeanRegistry#getSingleton(String beanName, boolean allowEarlyReference)打上点断,然后放开上面的断点,让程序停在getSingleton(...)方法:

img从单例池earlySingletonObjects中获得person

整体图

img

img

我们发现,AnnotationConfigApplicationContext#getBean()方法,经过层层调用最终还是找到了DefaultSingletonBeanRegistry的earlySingletonObjects。

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

img

查看AnnotationConfigApplicationContext的类继承图应该可以看到它继承了DefaultSingletonBeanRegistry:

img不用找了,图中根本没有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()

img

我们来到了AbstractApplicationContext

img翻译:ApplicationContext接口的抽象实现类,只是简单地实现了普通的上下文功能,使用模板方法模式,具体方法实现留给子类

img请注意getBean()方法上面的注释:Implementation of BeanFactory interface,意味着这些getBean()方法是对BeanFactory的实现

卧槽,这注释写得也太人性化了吧,生怕你不知道AbstractApplicationContext实现了BeanFactory

img所以AnnotationConfigApplicationContext也算是间接实现了BeanFactory

AbstractApplicationContext#getBean()(既然实现了BeanFactory,就有getBean()方法)

@Override
public Object getBean(String name) throws BeansException {
    assertBeanFactoryActive();
    // 果然不出我们所料,AbstractApplicationContext内部好像组合了一个BeanFactory
    return getBeanFactory().getBean(name);
}

查看AbstractApplicationContext#getBeanFactory(),看看具体组合了哪个BeanFactory

img结果发现这里用了模板方法模式,具体的实现留给了子类,AbstractApplicationContext本身并没有组合BeanFactory

继续点下去,我们来到了AbstractApplicationContext的子类GenericApplicationContext

imgGenericApplicationContext说自己包含了一个BeanFactory

GenericApplicationContext#getBeanFactory()

img

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

img

所以,AnnotationConfigApplicationContext#getBean()整个过程应该是这样的:

img利用模板方法模式,把getBeanFactory()留给子类实现,最终GenericApplicationContext组合了一个DefaultListableBeanFactory

至此,之前种种矛盾都由DefaultListableBeanFactory解决了:

【测试代码得到的结论】
ApplicationContext继承了BeanFactory,AnnotationConfigApplicationContext是ApplicationContext子类,通过getBean()得到了单例对象。

【观察源码得到的结论】
DefaultSingletonBeanRegistry拥有单例池earlySingletonObjects,但它没有实现BeanFactory,没有getBean()方法对外界访问。

【矛盾】
那么AnnotationConfigApplicationContext#getBean()怎么访问earlySingletonObjects?

答案就在DefaultListableBeanFactory这个“中介”上:

img

imgDefaultListableBeanFactory是ApplicationContext与SingletonBeanRegistry的中介

小结:

img


ApplicationContext与BeanFactory

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

img

也就是说,除了BeanFactory,ApplicationContext还继承了很多乱七八糟的接口来扩展自己的功能,比如:

  • ResourcePatternResolver接口,继承自ResourceLoader,我们常说的包扫描就是它负责的
  • ApplicationEventPublisher接口,关系着Spring的监听机制
  • EnvironmentCapable接口,提供了获取环境变量的方法。环境变量意味着什么?关系着@PropertySource、@Value、@Profile等注解
  • ...

来看一下官方文档对ApplicationContext的解释:

Theorg.springframework.beansorg.springframework.context包是Spring Framework的IoC容器的基础。BeanFactory接口提供了能够管理任何类型对象的高级配置机制。ApplicationContext是BeanFactory的子接口,扩展了以下几点:

更容易与Spring的AOP特性集成
消息资源处理(用于国际化)
事件发布
应用程序层特定的上下文,如web应用程序中使用的WebApplicationContext。

简而言之,BeanFactory提供了配置框架和基本功能,而ApplicationContext添加了更多企业特定的功能。ApplicationContext是BeanFactory的一个完整超集。

img