Spring IoC源码分析(注解版) -- 上

最近有时间读了一下Spring的源码,顺便记录一下笔记,加深理解。关于Spring IoC源码分析,网上有一篇文章写得很详细,叫Spring IOC 容器源码分析,大家可以去看看。这篇文章是基于XML的,因为工作中基于注解的方式用得多一些,所以就读了一下Spring IoC注解版本的源代码。为了更方便的分析源码,有时候需要对代码一步一步进行调试,所以我写了一个简单的spring-ioc-analysis,放在github上,供大家参考。

整个分析过程大概分为4篇(上,中,下和总结篇),因为时间和水平有限,笔记中如果有错误之处,欢迎大家指正。

核心概念

在分析Spring IoC源码之前,需要弄清楚一些核心概念。

BeanDefinition

这个是一个接口,它是用来存储Bean定义的一些信息的,比如ClassName,Scope,init-methon,等等。它的实现有RootBeanDefination,AnnotatedGenericBeanDefinition等。

BeanDefinitionHolder

这是BeanDefination的包装类,用来存储BeanDefinition,name以及aliases等。

BeanFactory & AbstractBeanFactory

BeanFactory就是Bean工厂啦,所有的Bean都由BeanFactory统一创建和管理,Spring提供了一个AbstractBeanFactory,它继承了BeanFactory接口,并且实现了大部分BeanFactory的功能。

AbstractBeanFactory有一个非常重要的方法叫getBean()

public <T> T getBean(String name, Class<T> requiredType) throws BeansException {
        return doGetBean(name, requiredType, null, false);
    }

它是用来根据名字和类型获取Bean对象的。它获取对象的原理是,如果BeanFactory中存在Bean,则从缓存中取Bean,否则创建并返回该Bean,并且将该Bean添加到缓存中(这里指Singleton类型的Bean)。所以确切的讲,Bean是在什么时候创建的,要看什么时候调用getBean方法获取这个Bean,根据Bean定义的不同,getBean方法会在不同时候被调用。Spring中Bean大致可以分为两类,应用类Bean(被应用程序使用)以及功能性Bean(被Spring使用)。Spring提供了一些功能性接口如BeanFactoryPostProcessor和BeanPostProcessor,实现了这些接口的Bean可以成为功能性Bean。如果Bean实现了BeanFactoryPostProcessor,则在refresh方法中调用invokeBeanFactoryPostProcessors方法时创建Bean,如果实现了BeanPostProcessor方法,则在registerBeanPostProcessors方法中创建Bean。其它的Bean应该属于应用类Bean,在默认的finishBeanFactoryInitialization方法中创建Bean,这在后边还会讲到。

AnnotationConfigApplicationContext & BeanFactory & BeanDefinitionRegistry & DefaultListableBeanFactory

这几个类的关系也是很重要的,我们来捋一捋。

AnnotationConfigApplicationContext这个类是基于注解的容器类,它实现了BeanFactory和BeanDefinitionRegistry两个接口,拥有Bean对象的管理和BeanDefinition注册功能。同时这个类拥有一个DefaultListableBeanFactory的对象。

DefaultListableBeanFactory也实现了BeanFactory和BeanDefinitionRegistry接口,拥有Bean对象管理和BeanDefinition注册功能。AnnotationConfigApplicationContext是委托DefaultListableBeanFactory来实现对象管理和BeanDefinition注册的。这里使用的是代理模式。

介绍完了核心概念,我们来开始分析源码,剖析Spring 容器的加载过程。Spring容器启动过程主要做了两件事情:

  • 加载BeanDefinition
  • 创建Bean

我们先来看BeanDefinition的加载过程。

加载BeanDefinition

我们创建一个简单Maven工程,在main函数中输入如下代码,就启动了一个基于注解的Spring容器,并从容器中获取helloService对象。

public static void main(String[] args) {
        AbstractApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
        context.registerShutdownHook();
        HelloService helloService = context.getBean(HelloService.class);
        helloService.sayHello();
    }

之所以要使用AbstractApplicationContext类,是因为我们的HelloService实现了一些BeanPostProcessor这样的接口,需要调用AnnotationConfigApplicationContext对象的registerShutdownHook方法。

现在来分析源码,进入AnnotationConfigApplicationContext的构造函数:

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
        this();
        register(annotatedClasses);
        refresh();
    }

两个主方法,resister和refresh。

register

进入register方法,它调用了reader.register方法。

public void register(Class<?>... annotatedClasses) {
        Assert.notEmpty(annotatedClasses, "At least one annotated class must be specified");
        this.reader.register(annotatedClasses);
    }

这个reader是AnnotatedBeanDefinitionReader类的一个实例,在AnnotationConfigApplicationContext的构造函数中创建的。构造函数还创建了一个scanner,用于包扫描。

public AnnotationConfigApplicationContext() {
        this.reader = new AnnotatedBeanDefinitionReader(this);
        this.scanner = new ClassPathBeanDefinitionScanner(this);
    }

进入reader的register方法:


    public void register(Class<?>... annotatedClasses) {
        for (Class<?> annotatedClass : annotatedClasses) {
            registerBean(annotatedClass);
        }
    }

    public void registerBean(Class<?> annotatedClass) {
        doRegisterBean(annotatedClass, null, null, null);
    }


    <T> void doRegisterBean(Class<T> annotatedClass, @Nullable Supplier<T> instanceSupplier, @Nullable String name,
            @Nullable Class<? extends Annotation>[] qualifiers, BeanDefinitionCustomizer... definitionCustomizers) {

        // 创建BeanDefinition
        AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
        // 生成bean name
        String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));

        AnnotationConfigUtils.processCommonDefinitionAnnotations(abd);
        
        // ...省去了其它代码

        // 创建BeanDefinitionHolder,它是BeanDefiniton的一个包装类,包含BeanDefiniton对象和它的名字
        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
        // 最后调用registerBeanDefinition
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
    }

限于篇幅,我们只分析一些比较重要的源代码,其它的源代码都被我删除了。进入BeanDefinitionReaderUtils.registerBeanDefinition

public static void registerBeanDefinition(
            BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
            throws BeanDefinitionStoreException {

        // Register bean definition under primary name.
        String beanName = definitionHolder.getBeanName();
        // 调用registry的registerBeanDefinition方法,registry是我们传进来的AnnotationConfigApplicationContext,因为它实现了BeanDefinitionRegistry,所以我们进入AnnotationConfigApplicationContext的registerBeanDefinition方法。
        registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition());

        // Register aliases for bean name, if any.
        String[] aliases = definitionHolder.getAliases();
        if (aliases != null) {
            for (String alias : aliases) {
                registry.registerAlias(beanName, alias);
            }
        }
    }

进入AnnotationConfigApplicationContext的registerBeanDefinition方法。好奇怪,怎么没有呢,原来在它的父类GenericApplicationContext中。来看代码:

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {

        // 调用beanFactory的registerBeanDefinition。我们之前讲过,AnnotationConfigApplicationContext是委托DefaultListableBeanFactory来注册BeanDefinition和管理Bean的,所以调用了beanFactory的registerBeanDefinition
        this.beanFactory.registerBeanDefinition(beanName, beanDefinition);
    }

在GenericApplicationContext的构造函数中,创建了这个beanFactory。

public GenericApplicationContext() {
        // 前面也讲过,DefaultListableBeanFactory也实现了BeanDefinitionRegistry和BeanFactory接口的。
        this.beanFactory = new DefaultListableBeanFactory();
    }

既然beanFactory是DefaultListableBeanFactory的实例,所以进入DefaultListableBeanFactory的registerBeanDefinition方法咯。

public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
            throws BeanDefinitionStoreException {
    
        
        //... 省去重复注册代码逻辑以及其它一些逻辑判断,
    
        //最终执行以下代码,可以看出,最终是将beanDefinition放到beanDefinitionMap中。所有的beanDefinition都会放到这个map中,以beanName为主键。如果一个beanDefinition有多个别名,那个它会被注册多次。
        this.beanDefinitionMap.put(beanName, beanDefinition);
    }

好啦,到这里为止,register方法就分析完成了。但是bean定义全部注册完了吗,还没有。这里只是注册完了配置文件的bean definition。至于我们自己注入的类,或者通过扫描注入的类,在refresh方法中,我们会在下一篇分析。

总结

我们今天讲了Spring IoC容器源码分析的第一部分,主要讲了一些核心概念,以及BeanDefinition的加载过程。

在Spring中,所有用户定义的Bean都会以BeanDefinition的方式注册到BeanFactory中。BeanDefinition相当于Bean的元数据,描述了Bean定义的一些属性。

我们讲了AnnotationConfigApplicationContext,BeanFactory,BeanDefinitionRegistry, DefaultListableBeanFactory。AnnotationConfigApplicationContext实现了BeanFactory和BeanDefinitionRegistry接口,DefaultListableBeanFactory也实现了DefaultListableBeanFactory接口。而AnnotationConfigApplicationContext中BeanDefinition的注册和Bean的管理,是委托给DefaultListableBeanFactory去完成的。

另外我们也分析了BeanDefinition的register过程,也确认了AnnotationConfigApplicationContext注册BeanDefinition确实是委托给DefaultListableBeanFactory来完成的。最终BeanDefinition被存储在一个Map中,以beanName为key,以BeanDefinition对象为值。

我们下一篇会继续讲refresh方法。


所有文章在Github上同步,你也可以访问我的个人博客点击查看

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343