簡單實現(xiàn)Spring的IoC功能

簡化版Spring

Github上有個Spring功能簡化的項目:https://github.com/code4craft/tiny-spring ,簡單包含了Spring的IoC以及AOP功能怜跑。本文簡單介紹了它的IoC實現(xiàn)样勃,AOP以后有空再說。

tiny-spring是為了學(xué)習(xí)Spring的而開發(fā)的,可以認為是一個Spring的精簡版彤灶。Spring的代碼很多看幼,層次復(fù)雜,閱讀起來費勁幌陕。我嘗試從使用功能的角度出發(fā)诵姜,參考Spring的實現(xiàn),一步一步構(gòu)建搏熄,最終完成一個精簡版的Spring棚唆。有人把程序員與畫家做比較,畫家有門基本功叫臨摹心例,tiny-spring可以算是一個程序的臨摹版本-從自己的需求出發(fā)宵凌,進行程序設(shè)計,同時對著名項目進行參考止后。

下面開始進入tiny-spring的項目構(gòu)建瞎惫。

從XML中讀取Bean信息

從XML中讀取信息自然需要XML文件的輸入流InputStream,因此我們對InputStream流的獲取進行約束:

public interface Resource {
    InputStream getInputStream() throws IOException;
}

增加一個子接口ResourceLoader繼承Resource接口:

public interface ResourceLoader extends Resource {
    Resource getResource(String location);
}

最后译株,使用實現(xiàn)類獲取指定位置文件的InputStream

public class UrlResourceLoader implements ResourceLoader {

    private URL url;

    @Override
    public InputStream getInputStream() throws IOException{
        URLConnection urlConnection = url.openConnection();
        urlConnection.connect();
        return urlConnection.getInputStream();
    }

    @Override
    public Resource getResource(String location) {
        URL resource = this.getClass().getClassLoader().getResource(location);
        url = resource;
        return this;
    }
}

測試看看:

    @Test
    public void test() throws IOException {
        ResourceLoader resourceLoader = new UrlResourceLoader();
        Resource resource = resourceLoader.getResource("tinyioc.xml");
        InputStream inputStream = resource.getInputStream();
        Assert.assertNotNull(inputStream);
    }

可以看到瓜喇,獲取指定XML文件的輸入流可以通過測試。

文件讀取沒有問題后歉糜,剩下的就是解析以及保存讀取的內(nèi)容了乘寒。

BeanDefinition

Spring使用BeanDefinition接口定義作為Bean實例和XML配置文件之間的中間層。

為簡便起見匪补,tiny-springBeanDefinition定義為一個類:

public class BeanDefinition {

    private Object bean;

    private Class beanClass;

    private String beanClassName;

    private PropertyValues propertyValues = new PropertyValues();

    public BeanDefinition() {
    }

    //Getter伞辛、Setter
}

PropertyValues屬性則封裝了從XML中讀取的屬性:

public class PropertyValues {
    private final List<PropertyValue> propertyValueList = new ArrayList<PropertyValue>();
    public PropertyValues() {
    }
    public void addPropertyValue(PropertyValue pv) {
        //TODO:這里可以對于重復(fù)propertyName進行判斷,直接用list沒法做到
        this.propertyValueList.add(pv);
    }
    public List<PropertyValue> getPropertyValues() {
        return this.propertyValueList;
    }
}

其實就是key-value:

public class PropertyValue {
    private final String name;
    private final Object value;
    public PropertyValue(String name, Object value) {
        this.name = name;
        this.value = value;
    }
    //Getter夯缺、Setter
}

上面說過如何從指定文件獲取InputStream蚤氏。

UrlResourceLoader只是提供了獲取InputStream的方法,保存從XML中讀取轉(zhuǎn)化出的BeanDefinition還需要一個類進行保存踊兜,tiny-spring為從XML中加載BeanDefinition安排了接口BeanDefinitionReader

public interface BeanDefinitionReader {
    void loadBeanDefinitions(String location) throws Exception;
}

從指定文件中讀取配置信息竿滨。它的實現(xiàn)類AbstractBeanDefinitionReader則擴充了該接口的功能,同時保存從XML中讀取出的BeanDefinition

public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader{

    private Map<String,BeanDefinition> registry;

    private ResourceLoader resourceLoader;

    protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
        this.registry = new HashMap<String, BeanDefinition>();
        this.resourceLoader = resourceLoader;
    }
    //Getter润文、Setter
}

這里的registry我覺得可以與后續(xù)BeanFactory的beanDefinitionMap整合在一起姐呐,不需要將BeanDefinition的集合存兩個地方。

registry屬性將BeanDefinitionname作為key典蝌, BeanDefinition自身作為value保存在Map中曙砂。

最后,由XmlBeanDefinitionReader實現(xiàn)具體的方法:

public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

    public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
        super(resourceLoader);
    }

    @Override
    public void loadBeanDefinitions(String location) throws Exception {
        InputStream inputStream = getResourceLoader().getResource(location).getInputStream();
        doLoadBeanDefinitions(inputStream);
    }
}

接下來就很明顯了骏掀,doLoadBeanDefinitions需要對XML文件做詳細的處理鸠澈,包括將屬性名以及屬性值構(gòu)建為PropertyValues柱告、獲取一個Bean對另一個Bean的引用等。

說到這Bean引用笑陈,當(dāng)前的工具類不能解決一個Bean對另一個Bean的引用际度,所以這里還需要引入一個代表類引用的類BeanReference

public class BeanReference {
    private String name;
    private Object bean;
    //Getter、Setter
}

這樣涵妥,tiny-spring就可以完成XML配置到BeanDefinition的轉(zhuǎn)換了乖菱。

BeanFactory

眾所周知,BeanFactory是Spring的核心容器接口蓬网。如果是借助BeanFactory操作Bean的話窒所,用的最多的一個方法就是getBean()

比如:

HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");

即從Spring容器中獲取名為"helloWorldService"的Bean實例帆锋。

如果想要模仿Spring的話吵取,一個BeanFactory接口必不可少,它代表著Spring對外暴露的接口:

public interface BeanFactory {
    Object getBean(String name) throws Exception;
}

有了BeanFactory自然需要實現(xiàn)類锯厢,比如保存實例化后的Bean集合皮官。考慮到對于XML的處理已經(jīng)由XmlBeanDefinitionReader等類完成实辑,并已經(jīng)生成了BeanDefinition集合捺氢。因此,BeanFactory及其實現(xiàn)類的重點在于完成PropertyValue的賦值以及Bean的實例化徙菠,包括處理依賴注入等讯沈。

getBean()方法無疑是最重要的一個方法郁岩,不妨借助抽象類AbstractBeanFactory對其做一個初步的實現(xiàn):

    @Override
    public Object getBean(String name) throws Exception {
        // 嘗試從緩存中獲取BeanDefinition(BeanDefinition中維護了Bean的實例)
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        if (beanDefinition == null) {
            throw new IllegalArgumentException("No bean named " + name + " is defined");
        }
        Object bean = beanDefinition.getBean();
        if (bean == null) {
            // 實例化Bean
            bean = doCreateBean(beanDefinition);
            beanDefinition.setBean(bean);
        }
        return bean;
    }

實例化Bean方法:

    protected Object doCreateBean(BeanDefinition beanDefinition) throws Exception {
        Object bean = createBeanInstance(beanDefinition);
        beanDefinition.setBean(bean);
        applyPropertyValues(bean, beanDefinition);
        return bean;
    }
    protected Object createBeanInstance(BeanDefinition beanDefinition) throws Exception {
        return beanDefinition.getBeanClass().newInstance();
    }
    /**
     * 空方法婿奔,留給子類實現(xiàn)
     * @param bean
     * @param beanDefinition
     * @throws Exception
     */
    protected void applyPropertyValues(Object bean, BeanDefinition beanDefinition) throws Exception {
    }

applyPropertyValues類似于模板設(shè)計模式,即確定步驟的執(zhí)行順序问慎,但某些步驟的具體實現(xiàn)還未知萍摊。

來一個子類AutowireCapableBeanFactory實現(xiàn)該方法:

public class AutowireCapableBeanFactory extends AbstractBeanFactory {

    protected void applyPropertyValues(Object bean, BeanDefinition mbd) throws Exception {
        for (PropertyValue propertyValue : mbd.getPropertyValues().getPropertyValues()) {
            Object value = propertyValue.getValue();
            // 處理Bean引用
            if (value instanceof BeanReference) {
                BeanReference beanReference = (BeanReference) value;
                value = getBean(beanReference.getName());
            }

            try {
                // 執(zhí)行類的set方法
                Method declaredMethod = bean.getClass().getDeclaredMethod(
                        "set" + propertyValue.getName().substring(0, 1).toUpperCase()
                                + propertyValue.getName().substring(1), value.getClass());
                declaredMethod.setAccessible(true);

                declaredMethod.invoke(bean, value);
            } catch (NoSuchMethodException e) {
                Field declaredField = bean.getClass().getDeclaredField(propertyValue.getName());
                declaredField.setAccessible(true);
                declaredField.set(bean, value);
            }
        }
    }
}

測試

        // 1.讀取配置
        XmlBeanDefinitionReader xmlBeanDefinitionReader = new XmlBeanDefinitionReader(new UrlResourceLoader());
        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
        HelloWorldService helloWorldService = (HelloWorldService) beanFactory.getBean("helloWorldService");
        Assert.assertNotNull(helloWorldService);

沒問題,詳細代碼可以訪問tiny-spring項目獲取如叼。

小結(jié)

Spring源碼實在龐大冰木,新手很難一下子適應(yīng),尤其是它錯綜復(fù)雜的繼承以及實現(xiàn)關(guān)系笼恰,看多了頭疼踊沸。

tiny-spring構(gòu)建了簡單的IoC以及AOP實現(xiàn),像咱這樣的新手看看還挺不錯社证。

參考

  1. tiny-spring
  2. tiny-spring 分析
  3. 模板方法模式(模板方法設(shè)計模式)詳解
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逼龟,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子追葡,更是在濱河造成了極大的恐慌腺律,老刑警劉巖奕短,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異匀钧,居然都是意外死亡翎碑,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門之斯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來日杈,“玉大人,你說我怎么就攤上這事佑刷〈镆” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵项乒,是天一觀的道長啰劲。 經(jīng)常有香客問我,道長檀何,這世上最難降的妖魔是什么蝇裤? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮频鉴,結(jié)果婚禮上栓辜,老公的妹妹穿的比我還像新娘。我一直安慰自己垛孔,他們只是感情好藕甩,可當(dāng)我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著周荐,像睡著了一般狭莱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上概作,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天腋妙,我揣著相機與錄音,去河邊找鬼讯榕。 笑死骤素,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的愚屁。 我是一名探鬼主播济竹,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼霎槐!你這毒婦竟也來了送浊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤栽燕,失蹤者是張志新(化名)和其女友劉穎罕袋,沒想到半個月后改淑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡浴讯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年朵夏,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榆纽。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡仰猖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奈籽,到底是詐尸還是另有隱情饥侵,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布衣屏,位于F島的核電站躏升,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏狼忱。R本人自食惡果不足惜膨疏,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望钻弄。 院中可真熱鬧佃却,春花似錦、人聲如沸窘俺。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瘤泪。三九已至灶泵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間均芽,已是汗流浹背丘逸。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工单鹿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留掀宋,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓仲锄,卻偏偏與公主長得像劲妙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子儒喊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容