文档章节

1000行代码读懂Spring(一)- 实现一个基本的IoC容器

黄亿华
 黄亿华
发布于 2014/01/13 14:04
字数 1348
阅读 16757
收藏 225

引言

最近在读Spring源码,但是Spring代码层次嵌套太多,读起来有很大跳跃性,我有个朋友甚至开玩笑说,读Spring得拿纸笔,把方法和层次都写下来。

其实Spring我已经接触很久了,记得大学有个老师说过:“学一门技术,最好是先思考一下,如果是你,会怎么实现,再带着问题去学习它”。也有人把程序员与画家做比较,画家有门基本功叫临摹,我想程序员是不是也可以用这样的方式,学习一下世界顶级的项目的编程方法?

于是就有了tiny-spring。这个项目是从我的使用场景出发,理解Spring的功能,并且一步一步完善出来的。类和方法命名基本都是照搬Spring的,包括一些配置格式都相同。这个项目我会控制在1000行以内,但是会尽量覆盖Spring的IoC和AOP核心功能。

tiny-spring是逐步进行构建的,里程碑版本我都使用了git tag来管理。例如,最开始的tag是step-1-container-register-and-get,那么可以使用

git checkout step-1-container-register-and-get

来获得这一版本。

这次主要是学习IoC部分,以下是各版本的记录:

1.step1-最基本的容器

git checkout step-1-container-register-and-get

IoC最基本的角色有两个:容器(BeanFactory)和Bean本身。这里使用BeanDefinition来封装了bean对象,这样可以保存一些额外的元信息。测试代码:

// 1.初始化beanfactory
BeanFactory beanFactory = new BeanFactory();

// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition(new HelloWorldService());
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 3.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

2.step2-将bean创建放入工厂

git checkout step-2-abstract-beanfactory-and-do-bean-initilizing-in-it

step1中的bean是初始化好之后再set进去的,实际使用中,我们希望容器来管理bean的创建。于是我们将bean的初始化放入BeanFactory中。为了保证扩展性,我们使用Extract Interface的方法,将BeanFactory替换成接口,而使用AbstractBeanFactoryAutowireCapableBeanFactory作为其实现。"AutowireCapable"的意思是“可自动装配的”,为我们后面注入属性做准备。

 // 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();

// 2.注入bean
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 3.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

3.step3-为bean注入属性

git checkout step-3-inject-bean-with-property

这一步,我们想要为bean注入属性。我们选择将属性注入信息保存成PropertyValue对象,并且保存到BeanDefinition中。这样在初始化bean的时候,我们就可以根据PropertyValue来进行bean属性的注入。Spring本身使用了setter来进行注入,这里为了代码简洁,我们使用Field的形式来注入。

// 1.初始化beanfactory
BeanFactory beanFactory = new AutowireCapableBeanFactory();

// 2.bean定义
BeanDefinition beanDefinition = new BeanDefinition();
beanDefinition.setBeanClassName("us.codecraft.tinyioc.HelloWorldService");

// 3.设置属性
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("text", "Hello World!"));
beanDefinition.setPropertyValues(propertyValues);

// 4.生成bean
beanFactory.registerBeanDefinition("helloWorldService", beanDefinition);

// 5.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

4.step4-读取xml配置来初始化bean

git checkout step-4-config-beanfactory-with-xml

这么大一坨初始化代码让人心烦。这里的BeanDefinition只是一些配置,我们还是用xml来初始化吧。我们定义了BeanDefinitionReader初始化bean,它有一个实现是XmlBeanDefinitionReader

// 1.读取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2.初始化BeanFactory并注册bean
BeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
        beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

5.step5-为bean注入bean

git checkout step-5-inject-bean-to-bean

使用xml配置之后,似乎里我们熟知的Spring更近了一步!但是现在有一个大问题没有解决:我们无法处理bean之间的依赖,无法将bean注入到bean中,所以它无法称之为完整的IoC容器!如何实现呢?我们定义一个BeanReference,来表示这个属性是对另一个bean的引用。这个在读取xml的时候初始化,并在初始化bean的时候,进行解析和真实bean的注入。

for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
    Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
    declaredField.setAccessible(true);
    Object value = propertyValue.getValue();
    if (value instanceof BeanReference) {
        BeanReference beanReference = (BeanReference) value;
        value = getBean(beanReference.getName());
    }
    declaredField.set(bean, value);
}

同时为了解决循环依赖的问题,我们使用lazy-init的方式,将createBean的事情放到getBean的时候才执行,是不是一下子方便很多?这样在注入bean的时候,如果该属性对应的bean找不到,那么就先创建!因为总是先创建后注入,所以不会存在两个循环依赖的bean创建死锁的问题。

// 1.读取配置
XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new ResourceLoader());
xmlBeanDefinitionReader.loadBeanDefinitions("tinyioc.xml");

// 2.初始化BeanFactory并注册bean
AbstractBeanFactory beanFactory = new AutowireCapableBeanFactory();
for (Map.Entry<String, BeanDefinition> beanDefinitionEntry : xmlBeanDefinitionReader.getRegistry().entrySet()) {
    beanFactory.registerBeanDefinition(beanDefinitionEntry.getKey(), beanDefinitionEntry.getValue());
}

// 3.初始化bean
beanFactory.preInstantiateSingletons();

// 4.获取bean
HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
helloWorldService.helloWorld();

6.step6-ApplicationContext登场

git checkout step-6-invite-application-context

现在BeanFactory的功能齐全了,但是使用起来有点麻烦。于是我们引入熟悉的ApplicationContext接口,并在AbstractApplicationContextrefresh()方法中进行bean的初始化工作。

ApplicationContext applicationContext = new ClassPathXmlApplicationContext("tinyioc.xml");
HelloWorldService helloWorldService = (HelloWorldService) applicationContext.getBean("helloWorldService");
helloWorldService.helloWorld();

是不是非常熟悉?至此为止,我们的tiny-spring的IoC部分可说完工了。这部分的类、方法命名和作用,都是对应Spring中相应的组件。虽然代码量只有400多行,但是已经有了基本的IoC功能!

项目地址

最后补充一下项目地址:https://github.com/code4craft/tiny-spring

© 著作权归作者所有

黄亿华

黄亿华

粉丝 2427
博文 131
码字总数 116344
作品 7
程序员
私信 提问
加载中

评论(37)

shiqiang1
shiqiang1

引用来自“linapex”的评论

还需要怎么读懂?

IOC

1.解析Xml/注解
2.处理依赖关系,实例化对象
3.存入对象工厂

Aop
在IOC上扩展(注解或是xml方式)

其他的模块(ORM,JDBC,TX,WEBMVC)都是扩展,spring 提供了足够多的扩展点。里面的代码功能分的很细。

我记得张孝祥的Spring 2.5 视频教程讲的很清晰。原理 so easy.

对,找了好久才找到这篇文章,如果其他的源码像 zookeeper和tomcat hibernate等也有类似的文章就好了。原理都东,就是怎样自己创造好难👍
shiqiang1
shiqiang1

引用来自“linapex”的评论

还需要怎么读懂?

IOC

1.解析Xml/注解
2.处理依赖关系,实例化对象
3.存入对象工厂

Aop
在IOC上扩展(注解或是xml方式)

其他的模块(ORM,JDBC,TX,WEBMVC)都是扩展,spring 提供了足够多的扩展点。里面的代码功能分的很细。

我记得张孝祥的Spring 2.5 视频教程讲的很清晰。原理 so easy.

如果和你说的,一个鼻子连个眼睛都是人,so easy
算法与编程之美
算法与编程之美
写的非常好,感谢!对我的教学有很大的帮助,谢谢!
不死鸟哇
不死鸟哇
博主好,这里的XMLWebApplicationContext.loadBeanDefinitions方法是设置监听器contextListner,然后通过contxtLoader起加载,然后被调用的。。想问下AnnotationConfigWebApplicationContext.loadBeanDefinitions这个加载注解标识的bean的方法是什么时候被调用的呢?
墙头的北极星

引用来自“fanfuxiaoran”的评论

同问 31楼,checkout不是用来切换分支么?

不好意思,明白了
墙头的北极星
同问 31楼,checkout不是用来切换分支么?
Lambada_张前
Lambada_张前
对不起,弱弱的问一个问题,怎么下载不了里程碑版本啊?
沙发迪
沙发迪
非常好!
哈库纳
哈库纳

引用来自“linapex”的评论

引用来自“黄勇”的评论

这里好热闹!我的观点是:想研究源码就去研究,想自己写框架就去写。只要觉得快乐,那就去做!
我想说的是,对快乐而言,却有 4 种理解:
1. 让自己快乐,也让别人快乐(这种人叫“雷锋”)
2. 让自己快乐,不管别人是否快乐(这种人叫“自私”)
3. 让自己不快乐,也让别人不快乐(这种人叫“无聊”)
4. 让自己不快乐,让别人快乐(这种人叫“傻子”)
这年头“雷锋”已经不多了,至少我认为你们都是:@黄亿华、@哈库纳、@linapex、@悠悠然然

雷锋GG都被你叫出来了。我个人能当选,2727 真是汗颜。

我很喜欢马云的一句话,你喜欢,你就去做。不要考虑失败和结果,因为你喜欢。

@黄勇的 【Java那点事儿】,拜读过,写的挺好,通俗易懂。

@黄勇 写的博文绝对一流。
Timco
Timco
围观一下楼上@_@
三条路线告诉你如何掌握Spring IoC容器的核心原理

一、前言 前三篇已经从历史的角度和大家一起探讨了为什么会有Spring,Spring的两个核心概念:IoC和AOP的雏形,Spring的历史变迁和如今的生态帝国。本节的主要目的就是通过一个切入点带大家一...

Java小铺
2018/08/27
0
0
Spring中ApplicationContext和beanfactory区别

org.springframework.beans及org.springframework.context包是Spring IoC容器的基础。BeanFactory提供的高级配置机制,使得管理任何性质的对象成为可能。ApplicationContext是BeanFactory的扩...

ArlenXu
2015/01/18
632
0
Spring BeanFactory和FactoryBean的区别

有些东西可能知其用,但也需知其所以然,特转载该篇Spring BeanFactory和FactoryBean的区别 及 包是 Spring IoC 容器的基础。 一、BeanFactory 定义了IOC容器的最基本形式,并提供了IOC容器应...

淡淡的倔强
2018/09/07
0
0