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

引言

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

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

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

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

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

来得到这一版本。spring

此次主要是学习IoC部分,如下是各版本的记录:编程

1.step1-最基本的容器

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

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

// 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,它有一个实现是XmlBeanDefinitionReadercode

// 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

相关文章
相关标签/搜索