1. 程式人生 > >自己動手開發IOC容器

自己動手開發IOC容器

前兩天寫簡歷,寫了一句:精通Spring IoC容器。怎麼個精通法?還是自己動手寫個IOC容器吧。

什麼是IoC(Inversion of Control)?什麼是DI(Dependency Injection)?不多解釋,自己Google!希望你先明確幾個概念,該文章不做這方面的闡述,重點關注如何實現。我將用簡要的語言從需求,設計,程式碼三方面來描述一個簡單的IoC容器,來說明IoC容器是如何幫我們建立類的例項、如何實現依賴注入,最後會奉上一個完整的IoC容器例項。

一、需求,實現一個IoC容器

1、需求描述:


2、圖中一些概念:

(1)服務元資料:一種是我們的編譯好的類檔案,即.class檔案;另一種是xml檔案。你可以理解為spring的註解和xml兩種配置bean的方式。

(2)服務定義資訊:由元資料轉換來的資訊,即讀取bean中的註解或xml檔案,獲取到的物件描述資訊。

(3)服務提供者:物件的例項。你可以理解為Spring中Bean的例項;

(4)服務定義資訊儲存區:儲存服務定義資訊的地方;

(5)服務提供者例項快取區:儲存服務提供者的地方。

3、流程描述:

(1)建立容器:初始化容器,實質是初始化容器內部的工廠,然後讀取元資料,轉化為服務定義資訊,儲存到服務定義資訊儲存區;

(2)呼叫服務提供者A的方法:獲取該服務提供者A的服務定義資訊,判斷是否有快取,如果有,直接呼叫快取中的A;如果沒有,據此例項化A,放入快取,若發現A依賴其他服務提供者B,則查詢快取,如果有,將快取中的B注入A;否則例項化B,注入到A中,同時放入快取。以此類推。

(3)上面兩個是核心流程,在此基礎上,擴充套件了幾個流程:

     1)獲取所有服務提供者定義資訊;

    2)獲取所有服務提供者例項;

    3)獲取某個服務提供者例項;

    4)熱載入新的服務提供者。

二、設計,根據需求,以面向物件為基礎,融合幾種設計模式來勾勒IOC容器。

注:由於原始碼較多,會佔很大篇幅,所以只給出了部分核心程式碼實現,所有程式碼見我的CSDN CODE,見文章末尾的說明。

1、UML,類圖


2、設計說明

從類圖可以看出,嚴格按照面向介面的方式程式設計,鬆耦合的設計保證了可擴充套件性和靈活性。

我定義了一套實現IOC容器的規範,這套規範的直接表現就是一組介面及其關係,有沒有一點J2EE的感覺。然後,我給出了各個介面的一個預設實現,也就是給出了一個容器的預設實現。其他開發者既可以全部我的預設實現或全部使用自定義實現,也可以部分元件使用預設實現,部分元件實現自定義實現。

3、介紹介面與預設實現,即擴充套件點。

(1)Container介面及預設實現


核心程式碼:

import com.tgb.Entity.BeanEntity;
import com.tgb.data.IDataContext;

/**
 * @Author : JiaLin
 * @Email : [email protected]
 * @Date : 2014/6/22
 * @Description :容器的核心介面
 */

public interface IContainer{

    //獲取容器的某個服務提供者例項
    public Object getBean(String name);

    //根據服務提供者名稱,服務名稱,引數來獲取容器提供的服務
    //由三者確定一個唯一的服務。
    public Object getService(String beanName,String serviceName,Object... args);

    //獲取容器所有服務描述資訊
    public IDataContext getBeanDefinitionContext();

    //獲取容器中某個服務提供者的描述資訊
    public BeanEntity getBeanDefinition(String name);

    //獲取容器中所有服務提供者例項的快取
    public IDataContext getBeanCacheContext();

    //熱載入新的服務提供者
    public void initContainerService(String resource);

}

說明:Container採用外觀模式,充當Factory的外觀層。Container可以自定義實現,這是擴充套件點一。而且使用了工廠模式和策略模式實現容器內部具體工廠的動態載入。

(2)Factory介面與預設實現

核心程式碼:

import com.tgb.Entity.BeanEntity;
import com.tgb.data.IDataContext;
import com.tgb.handler.HandlerDecorator;

import java.io.Serializable;

/**
 * @Author : JiaLin
 * @Email : [email protected]
 * @Date : 2014/6/22
 * @Description : 抽象的服務工廠,定義處理服務的演算法骨架,具體由其子類實現。
 */


//使用模板方法模式定義演算法的骨架,具體實現有相應的子類去做。
public abstract class AbstractBeanFactory implements IBeanFactory, Serializable {

    //----------元件初始化----------begin-----

    protected IDataContext beanDefinitionContext;//服務描述資訊的儲存區
    protected IDataContext beanCacheContext; //服務提供者例項的快取區
    protected HandlerDecorator handlerDecorator;//轉換器(元資料到服務描述資訊)

    //設定服務描述資訊的儲存區
    public abstract void setBeanDefinitionContext(String beanDefinitionContextName, String resource);

    //設定服務提供者例項的快取區
    public abstract void setBeanCacheContext(String beanCacheContextName);

    //設定轉換器(元資料到服務描述資訊)
    public abstract void setHandler(String defaultHandler, String handlerName);

    @Override
    //模板方法
    //註冊服務元件,初始化服務描述資訊
    public final void registerBeanDefinition(String resource, String cacheContext, String definitionContext, String defaultHandler, String customHandler) {

        this.setHandler(defaultHandler, customHandler);
        this.setBeanCacheContext(cacheContext);
        this.setBeanDefinitionContext(definitionContext, resource);
    }

    //----------元件初始化----------end-----


    //----------服務提供者的生命週期-----begin--------

    //獲取某個服務提供者的服務描述資訊
    public abstract BeanEntity getBeanDefinition(String name);

    //檢查該服務提供者的例項是否有快取
    public abstract boolean containsBeanCache(String name);

    //從快取中獲取服務提供者的例項
    public abstract Object getBeanCache(String name);

    //建立服務提供者
    public abstract Object creatBean(BeanEntity beanEntity);

    //將服務提供者例項註冊到快取
    public abstract Object regiterBeanCache(String name, Object obj);

    //----------服務提供者的生命週期-----end--------

    @Override
    //模板方法
    //獲取服務提供者例項
    public final Object getBean(String name) {

        //獲取某個Bean的定義
        BeanEntity beanEntity = getBeanDefinition(name);

        //判斷這個Bean是否已經載入到快取,如果有,直接返回
        if (containsBeanCache(name)) {
            return getBeanCache(name);
        }

        //建立bean的例項
        Object beanObject = this.creatBean(beanEntity);

        //註冊到快取
        regiterBeanCache(name, beanObject);

        //返回bean的例項
        return beanObject;
    }

    //獲取所有的服務描述資訊
    public abstract IDataContext getBeanDefinitionContext();

    //獲取所有的服務例項快取資訊
    public abstract IDataContext getBeanCacheContext();

}

說明:AbstractBeanFactory採用模板方法模式,AbstractBeanFactory中定義了初始化服務定義資訊和獲取服務提供者例項兩個模板方法,定義了演算法的骨架。模板方法依賴的方法的實現交給子類去完成,這裡交給DefaultBeanFactory完成。可以實現自己的BeanFactory,這是擴充套件點二。

(3)DataContext的介面及實現


   核心程式碼:

import java.util.Map;

/**
 * @Author : JiaLin
 * @Email : [email protected]
 * @Date : 2014/6/22
 * @Description :抽象的資料儲存介面
 */
public interface IDataContext {

    public  void initData(Map<String,Object> map);

    public void set(String name,Object obj);

    public Object get(String name);

    public Map<String,Object> getAll();
}

說明:IDataContext定義了儲存區域應該有的方法,兩個實現,DefaultBeanDefinitionContext是服務提供者描述資訊儲存的預設實現;DefaultBeanCacheContext是服務提供者例項儲存的預設實現。可以自定義儲存區實現,這是擴充套件點三。

(4)Handler介面及實現


核心程式碼:

import java.util.Map;

/**
 * @Author : JiaLin
 * @Email : [email protected]
 * @Date : 2014/6/23
 * @Description :  抽象的處理器裝飾者
 */

//使用裝飾者模式,可以動態新增功能(這裡是動態新增元資料處理器)
//例如要擴充套件Annotation就需要自己擴充套件處理器,或者擴充套件xml處理器等等
public abstract class HandlerDecorator implements IHandler{

    protected IHandler handler;

    public void setHandler(IHandler handler){
        this.handler=handler;
    }


    @Override
    public Map<String, Object> convert(String string) {

        if(handler!=null)
        {
            return handler.convert(string);
        }

        return null;
    }
}

說明:這裡採用了一個裝飾者模式,預設提供了一個註解裝飾者DefaultAnnotationHandler,來把元資料中註解資訊轉換為服務提供者定義資訊。其他開發者可繼承HandlerDecorator,來實現自己的裝飾者,例如把xml資訊轉換為服務提供者定義資訊的Xmlhandler。工廠會依次實現這些裝飾者解析元資料。這是擴充套件點四。

(5)Annotation


核心程式碼:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author : JiaLin
 * @Email : [email protected]
 * @Date : 2014/6/22
 * @Description :用來標註服務的提供者
 */

@Retention(RetentionPolicy.RUNTIME)
@Target( {
        ElementType.FIELD,
        ElementType.TYPE
})
public @interface Bean {
    String name();
}

說明:這裡提供了三個註解來描述服務提供者的元資料,handler據此將元資料轉換為服務提供者定義資訊。其他開發者可擴充套件該Annotation,實現自己的Annotation,然後自定義解析的handler,重寫factory的createBean方法即可。這是擴充套件點五。

(5)定義資料結構,儲存服務定義資訊


核心程式碼:
import java.util.List;

/**
 * @Author : JiaLin
 * @Email : [email protected]
 * @Date : 2014/6/22
 * @Description :服務提供者描述資訊載體,其資料結構如下:
 *
 *                                       BeanEntity
 *
 *                     name               type                      List
 *                   @bean註解名          Bean型別              ServicEntity的arrayList
 *                                                     ServiceEntity……
 *
 *                                                 name           value               List
 *                                              @Service註解名      方法名          ParamEntity的ArrayList
 *                                                                               ParamEntity……
 *                                                                             name            value
 *                                                                           @param註解名       引數型別
 */

public class BeanEntity {
    private String name;  //服務提供者名
    private String type;  //服務提供者例項的型別
    private Object value;  //服務提供者例項
    private List<ServiceEntity> serviceEntityList;   //服務提供者提供的所有服務
    public BeanEntity(){}
    public BeanEntity(String name,String type)
    {
        this.name=name;
        this.type=type;
    }
    //省略get,set方法
   
}

(6)反射Util

說明:這是java反射的工具類,封裝了一些反射常用方法。

(7)熱載入監聽器


說明:這個監聽器,將監聽某個位置,如果加入新的服務提供者元資料,將被容器熱載入。

三、所有程式碼

1、上面介紹了需求,設計,以及核心程式碼,如果依然不過癮,那來看看全部程式碼吧。

我已將這個小專案開源,託管到CSDN CODE 公有專案,專案首頁:

2、大家可以幫著改進,也可以下載檢視,還有很多改進的地方,最近工作繁忙,有空會持續更新,歡迎提出寶貴意見。