徒手擼框架--實現IoC

原文地址:https://www.xilidou.com/2018/...java

Spring 做爲 J2ee 開發事實上的標準,是每一個Java開發人員都須要瞭解的框架。可是Spring 的 IoC 和 Aop 的特性,對於初級的Java開發人員來講仍是比較難於理解的。因此我就想寫一系列的文章給你們講解這些特性。從而可以進一步深刻了解 Spring 框架。git

讀完這篇文章,你將會了解:github

  • 什麼是依賴注入和控制反轉
  • Ioc有什麼用
  • Spring的 Ioc 是怎麼實現的
  • 按照Spring的思路開發一個簡單的Ioc框架

IoC 是什麼?

wiki百科的解釋是:spring

控制反轉(Inversion of Control,縮寫爲IoC),是面向對象編程中的一種設計原則,能夠用來減低計算機代碼之間的耦合度。其中最多見的方式叫作依賴注入(Dependency Injection,簡稱DI)。經過控制反轉,對象在被建立的時候,由一個調控系統內全部對象的外界實體,將其所依賴的對象的引用傳遞給它。也能夠說,依賴被注入到對象中。

Ioc 有什麼用?

看完上面的解釋你必定沒有理解什麼是 Ioc,由於是第一次看見上面的話也以爲雲裏霧裏。shell

不過經過上面的描述咱們能夠大概的瞭解到,使用IoC的目的是爲了解耦。也就是說IoC 是解耦的一種方法。編程

咱們知道Java 是一門面向對象的語言,在 Java 中 Everything is Object,咱們的程序就是由若干對象組成的。當咱們的項目愈來愈大,合做的開發者愈來愈多的時候,咱們的類就會愈來愈多,類與類之間的引用就會成指數級的增加。以下圖所示:json

這樣的工程簡直就是災難,若是咱們引入 Ioc 框架。由框架來維護類的生命週期和類之間的引用。咱們的系統就會變成這樣:api

這個時候咱們發現,咱們類之間的關係都由 IoC 框架負責維護類,同時將類注入到須要的類中。也就是類的使用者只負責使用,而不負責維護。把專業的事情交給專業的框架來完成。大大的減小開發的複雜度。數據結構

用一個類比來理解這個問題。Ioc 框架就是咱們生活中的房屋中介,首先中介會收集市場上的房源,分別和各個房源的房東創建聯繫。當咱們須要租房的時候,並不須要咱們四處尋找各種租房信息。咱們直接找房屋中介,中介就會根據你的需求提供相應的房屋信息。大大提高了租房的效率,減小了你與各種房東之間的溝通次數。app

Spring 的 IoC 是怎麼實現的

瞭解Spring框架最直接的方法就閱讀Spring的源碼。可是Spring的代碼抽象的層次很高,且處理的細節很高。對於大多數人來講不是太容易理解。我讀了Spirng的源碼之後以個人理解作一個總結,Spirng IoC 主要是如下幾個步驟。

1. 初始化 IoC 容器。
2. 讀取配置文件。
3. 將配置文件轉換爲容器識別對的數據結構(這個數據結構在Spring中叫作 BeanDefinition) 
4. 利用數據結構依次實例化相應的對象
5. 注入對象之間的依賴關係

本身實現一個IoC框架

爲了方便,咱們參考 Spirng 的 IoC 實現,去除全部與核心原理無關的邏輯。極簡的實現 IoC 的框架。 項目使用 json 做爲配置文件。使用 maven 管理 jar 包的依賴。

在這個框架中咱們的對象都是單例的,並不支持Spirng的多種做用域。框架的實現使用了cglib 和 Java 的反射。項目中我還使用了 lombok 用來簡化代碼。

下面咱們就來編寫 IoC 框架吧。

首先咱們看看這個框架的基本結構:

從宏觀上觀察一下這個框架,包含了3個package、在包 bean 中定義了咱們框架的數據結構。core 是咱們框架的核心邏輯所在。utils 是一些通用工具類。接下來咱們就逐一講解一下:

1. bean 定義了框架的數據結構

BeanDefinition 是咱們項目的核心數據結構。用於描述咱們須要 IoC 框架管理的對象。

@Data
@ToString
public class BeanDefinition {

    private String name;

    private String className;

    private String interfaceName;

    private List<ConstructorArg> constructorArgs;

    private List<PropertyArg> propertyArgs;

}

包含了對象的 name,class的名稱。若是是接口的實現,還有該對象實現的接口。以及構造函數的傳參的列表 constructorArgs 和須要注入的參數列表 `propertyArgs。

2. 再看看咱們的工具類包裏面的對象:

ClassUtils 負責處理 Java 類的加載,代碼以下:

public class ClassUtils {
    public static ClassLoader getDefultClassLoader(){
        return Thread.currentThread().getContextClassLoader();
    }
    public static Class loadClass(String className){
        try {
            return getDefultClassLoader().loadClass(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }
}

咱們只寫了一個方法,就是經過 className 這個參數獲取對象的 Class。

BeanUtils 負責處理對象的實例化,這裏咱們使用了 cglib 這個工具包,代碼以下:

public class BeanUtils {
    public static <T> T instanceByCglib(Class<T> clz,Constructor ctr,Object[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clz);
        enhancer.setCallback(NoOp.INSTANCE);
        if(ctr == null){
            return (T) enhancer.create();
        }else {
            return (T) enhancer.create(ctr.getParameterTypes(),args);
        }
    }
}

ReflectionUtils 主要經過 Java 的反射原理來完成對象的依賴注入:

public class ReflectionUtils {

    public static void injectField(Field field,Object obj,Object value) throws IllegalAccessException {
        if(field != null) {
            field.setAccessible(true);
            field.set(obj, value);
        }
    }
}

injectField(Field field,Object obj,Object value) 這個方法的做用就是,設置 obj 的 field 爲 value。

JsonUtils 的做用就是爲了解析咱們的json配置文件。代碼比較長,與咱們的 IoC 原理關係不大,感興趣的同窗能夠自行從github上下載代碼看看。

有了這幾個趁手的工具,咱們就能夠開始完成 Ioc 框架的核心代碼了。

3. 核心邏輯

個人 IoC 框架,目前只支持一種 ByName 的注入。因此咱們的 BeanFactory 就只有一個方法:

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

而後咱們實現了這個方法:

public class BeanFactoryImpl implements BeanFactory{

    private static final ConcurrentHashMap<String,Object> beanMap = new ConcurrentHashMap<>();

    private static final ConcurrentHashMap<String,BeanDefinition> beanDefineMap= new ConcurrentHashMap<>();

    private static final Set<String> beanNameSet = Collections.synchronizedSet(new HashSet<>());

    @Override
    public Object getBean(String name) throws Exception {
        //查找對象是否已經實例化過
        Object bean = beanMap.get(name);
        if(bean != null){
            return bean;
        }
        //若是沒有實例化,那就須要調用createBean來建立對象
        bean =  createBean(beanDefineMap.get(name));
        
        if(bean != null) {

            //對象建立成功之後,注入對象須要的參數
            populatebean(bean);
            
            //再把對象存入Map中方便下次使用。
            beanMap.put(name,bean;
        }

        //結束返回
        return bean;
    }

    protected void registerBean(String name, BeanDefinition bd){
        beanDefineMap.put(name,bd);
        beanNameSet.add(name);
    }

    private Object createBean(BeanDefinition beanDefinition) throws Exception {
        String beanName = beanDefinition.getClassName();
        Class clz = ClassUtils.loadClass(beanName);
        if(clz == null) {
            throw new Exception("can not find bean by beanName");
        }
        List<ConstructorArg> constructorArgs = beanDefinition.getConstructorArgs();
        if(constructorArgs != null && !constructorArgs.isEmpty()){
            List<Object> objects = new ArrayList<>();
            for (ConstructorArg constructorArg : constructorArgs) {
                objects.add(getBean(constructorArg.getRef()));
            }
            return BeanUtils.instanceByCglib(clz,clz.getConstructor(),objects.toArray());
        }else {
            return BeanUtils.instanceByCglib(clz,null,null);
        }
    }

    private void populatebean(Object bean) throws Exception {
        Field[] fields = bean.getClass().getSuperclass().getDeclaredFields();
        if (fields != null && fields.length > 0) {
            for (Field field : fields) {
                String beanName = field.getName();
                beanName = StringUtils.uncapitalize(beanName);
                if (beanNameSet.contains(field.getName())) {
                    Object fieldBean = getBean(beanName);
                    if (fieldBean != null) {
                        ReflectionUtils.injectField(field,bean,fieldBean);
                    }
                }
            }
        }
    }
}

首先咱們看到在 BeanFactory 的實現中。咱們有兩 HashMap,beanMap 和 beanDefineMap。 beanDefineMap 存儲的是對象的名稱和對象對應的數據結構的映射。beanMap 用於保存 beanName和實例化以後的對象。

容器初始化的時候,會調用 BeanFactoryImpl.registerBean 方法。把 對象的 BeanDefination 數據結構,存儲起來。

當咱們調用 getBean() 的方法的時候。會先到 beanMap 裏面查找,有沒有實例化好的對象。若是沒有,就會去beanDefineMap查找這個對象對應的 BeanDefination。再利用DeanDefination去實例化一個對象。

對象實例化成功之後,咱們還須要注入相應的參數,調用 populatebean()這個方法。在 populateBean 這個方法中,會掃描對象裏面的Field,若是對象中的 Field 是咱們IoC容器管理的對象,那就會調用 咱們上文實現的 ReflectionUtils.injectField來注入對象。

一切準備穩當以後,咱們對象就完成了整個 IoC 流程。最後這個對象放入 beanMap 中,方便下一次使用。

因此咱們能夠知道 BeanFactory 是管理和生成對象的地方。

4. 容器

咱們所謂的容器,就是對BeanFactory的擴展,負責管理 BeanFactory。咱們的這個IoC 框架使用 Json 做爲配置文件,因此咱們容器就命名爲 JsonApplicationContext。固然以後你願意實現 XML 做爲配置文件的容器你就能夠本身寫一個 XmlApplicationContext,若是基於註解的容器就能夠叫AnnotationApplcationContext。這些實現留個你們去完成。

咱們看看 ApplicationContext 的代碼:

public class JsonApplicationContext extends BeanFactoryImpl{
    private String fileName;
    public JsonApplicationContext(String fileName) {
        this.fileName = fileName;
    }
    public void init(){
        loadFile();
    }
    private void loadFile(){
        InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(fileName);
        List<BeanDefinition> beanDefinitions = JsonUtils.readValue(is,new TypeReference<List<BeanDefinition>>(){});
        if(beanDefinitions != null && !beanDefinitions.isEmpty()) {
            for (BeanDefinition beanDefinition : beanDefinitions) {
                registerBean(beanDefinition.getName(), beanDefinition);
            }
        }
    }
}

這個容器的做用就是 讀取配置文件。將配置文件轉換爲容器可以理解的 BeanDefination。而後使用 registerBean 方法。註冊這個對象。

至此,一個簡單版的 IoC 框架就完成。

5. 框架的使用

咱們寫一個測試類來看看咱們這個框架怎麼使用:

首先咱們有三個對象

public class Hand {
    public void waveHand(){
        System.out.println("揮一揮手");
    }
}

public class Mouth {
    public void speak(){
        System.out.println("say hello world");
    }
}

public class Robot {
    //須要注入 hand 和 mouth 
    private Hand hand;
    private Mouth mouth;

    public void show(){
        hand.waveHand();
        mouth.speak();
    }
}

咱們須要爲咱們的 Robot 機器人注入 hand 和 mouth。

配置文件:

[
  {
    "name":"robot",
    "className":"com.xilidou.framework.ioc.entity.Robot"
  },
  {
    "name":"hand",
    "className":"com.xilidou.framework.ioc.entity.Hand"
  },
  {
    "name":"mouth",
    "className":"com.xilidou.framework.ioc.entity.Mouth"
  }
]

這個時候寫一個測試類:

public class Test {
    public static void main(String[] args) throws Exception {
        JsonApplicationContext applicationContext = new JsonApplicationContext("application.json");
        applicationContext.init();
        Robot aiRobot = (Robot) applicationContext.getBean("robot");
        aiRobot.show();
    }
}

運行之後輸出:

揮一揮手
say hello world

Process finished with exit code 0

能夠看到咱們成功的給個人 aiRobot 注入了 hand 和 mouth。

至此咱們 Ioc 框架開發完成。

總結

這篇文章讀完之後相信你必定也實現了一個簡單的 IoC 框架。

雖說閱讀源碼是瞭解框架的最終手段。可是 Spring 框架做爲一個生產框架,爲了保證通用和穩定,源碼一定是高度抽象,且處理大量細節。因此 Spring 的源碼閱讀起來仍是至關困難。但願這篇文章可以幫助理解 Spring Ioc 的實現。

下一篇文章 應該會是 《徒手擼框架--實現AOP》。

github 地址

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息