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

相關文章
相關標籤/搜索