天天看點

自己動手實作一個簡單的 IOC,牛皮!!

1. 設計元件。

我們還記得Spring中最重要的有哪些元件嗎?BeanFactory 容器,BeanDefinition Bean的基本資料結構,當然還需要加載Bean的資源加載器。大概最後最重要的就是這幾個元件。

容器用來存放初始化好的Bean,BeanDefinition 就是Bean的基本資料結構,比如Bean的名稱,Bean的屬性 PropertyValue,Bean的方法,是否延遲加載,依賴關系等。資源加載器就簡單了,就是一個讀取XML配置檔案的類,讀取每個标簽并解析。

2. 設計接口

首先肯定需要一個BeanFactory,就是Bean容器,容器接口至少有2個最簡單的方法,一個是擷取Bean,一個注冊Bean.

/**
 * 需要一個beanFactory 定義ioc 容器的一些行為 比如根據名稱擷取bean, 比如注冊bean,參數為bean的名稱,bean的定義
 *
 * @author stateis0
 * @version 1.0.0
 * @Date 2017/11/30
 */public interface BeanFactory {

  /**
   * 根據bean的名稱從容器中擷取bean對象
   *
   * @param name bean 名稱
   * @return bean執行個體
   * @throws Exception 異常
   */
  Object getBean(String name) throws Exception;

  /**
   * 将bean注冊到容器中
   *
   * @param name bean 名稱
   * @param bean bean執行個體
   * @throws Exception 異常
   */
  void registerBeanDefinition(String name, BeanDefinition bean) throws Exception;}      

根據Bean的名字擷取Bean對象,注冊參數有2個,一個是Bean的名字,一個是 BeanDefinition 對象。

定義完了Bean最基本的容器,還需要一個最簡單 BeanDefinition 接口,我們為了友善,但因為我們這個不必考慮擴充,是以可以直接設計為類,BeanDefinition 需要哪些元素和方法呢?

需要一個 Bean 對象,一個Class對象,一個ClassName字元串,還需要一個元素集合 PropertyValues。這些就能組成一個最基本的 BeanDefinition 類了。那麼需要哪些方法呢?其實就是這些屬性的get set 方法。

我們看看該類的詳細:

package cn.thinkinjava.myspring;

/**
* bean 的定義
*
* @author stateis0
*/public class BeanDefinition {

  /**
   * bean
   */
  private Object bean;

  /**
   * bean 的 CLass 對象
   */
  private Class beanClass;

  /**
   * bean 的類全限定名稱
   */
  private String ClassName;

  /**
   * 類的屬性集合
   */
  private PropertyValues propertyValues = new PropertyValues();

  /**
   * 擷取bean對象
   */
  public Object getBean() {
    return this.bean;
  }

  /**
   * 設定bean的對象
   */
  public void setBean(Object bean) {
    this.bean = bean;
  }

  /**
   * 擷取bean的Class對象
   */
  public Class getBeanclass() {
    return this.beanClass;
  }

  /**
   * 通過設定類名稱反射生成Class對象
   */
  public void setClassname(String name) {
    this.ClassName = name;
    try {
      this.beanClass = Class.forName(name);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  /**
   * 擷取bean的屬性集合
   */
  public PropertyValues getPropertyValues() {
    return this.propertyValues;
  }

  /**
   * 設定bean的屬性
   */
  public void setPropertyValues(PropertyValues pv) {
    this.propertyValues = pv;
  }

}      

有了基本的 BeanDefinition 資料結構,還需要一個從XML中讀取并解析為 BeanDefinition 的操作類,首先我們定義一個 BeanDefinitionReader 接口,該接口隻是一個辨別,具體由抽象類去實作一個基本方法和定義一些基本屬性,比如一個讀取時需要存放的注冊容器,還需要一個委托一個資源加載器 ResourceLoader, 用于加載XML檔案,并且我們需要設定該構造器必須含有資源加載器,當然還有一些get set 方法。

package cn.thinkinjava.myspring;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.util.HashMap;
import java.util.Map;

/**
 * 抽象的bean定義讀取類
 *
 * @author stateis0
 *
/
public abstract class AbstractBeanDefinitionReader implements BeanDefinitionReader {

  /**
   * 注冊bean容器
   */
  private Map<String, BeanDefinition> registry;

  /**
   * 資源加載器
   */
  private ResourceLoader resourceLoader;

  /**
   * 構造器器必須有一個資源加載器, 預設插件建立一個map容器
   *
   * @param resourceLoader 資源加載器
   */
  protected AbstractBeanDefinitionReader(ResourceLoader resourceLoader) {
    this.registry = new HashMap<>();
    this.resourceLoader = resourceLoader;
  }

  /**
   * 擷取容器
   */
  public Map<String, BeanDefinition> getRegistry() {
    return registry;
  }

  /**
   * 擷取資源加載器
   */
  public ResourceLoader getResourceLoader() {
    return resourceLoader;
  }

}      

有了這幾個抽象類和接口,我們基本能形成一個雛形,BeanDefinitionReader 用于從XML中讀取配置檔案,生成 BeanDefinition 執行個體,存放在 BeanFactory 容器中,初始化之後,就可以調用 getBean 方法擷取初始化成功的Bean。形成一個完美的閉環。

3. 如何實作

剛剛我們說了具體的流程:從XML中讀取配置檔案, 解析成 BeanDefinition,最終放進容器。說白了就3步。那麼我們就先來設計第一步。

1. 從XML中讀取配置檔案, 解析成 BeanDefinition

我們剛剛設計了一個讀取BeanDefinition 的接口 BeanDefinitionReader 和一個實作它的抽象類 AbstractBeanDefinitionReader,抽象了定義了一些簡單的方法,其中由一個委托類-----ResourceLoader, 我們還沒有建立, 該類是資源加載器,根據給定的路徑來加載資源。

我們可以使用Java 預設的類庫 java.net.URL 來實作,定義兩個類,一個是包裝了URL的類 ResourceUrl, 一個是依賴 ResourceUrl 的資源加載類。

ResourceUrl 代碼實作

/**
 * 資源URL
 *
/
public class ResourceUrl implements Resource {

  /**
   * 類庫URL
   */
  private final URL url;

  /**
   * 需要一個類庫URL
   */
  public ResourceUrl(URL url) {
    this.url = url;
  }

  /**
   * 從URL中擷取輸入流
   */
  @Override
  public InputStream getInputstream() throws Exception {
    URLConnection urlConnection = url.openConnection();
    urlConnection.connect();
    return urlConnection.getInputStream();

  }
}      

ResourceLoader 實作

/**
 * 資源URL
*/
public class ResourceUrl implements Resource {

  /**
   * 類庫URL
   */
  private final URL url;

  /**
   * 需要一個類庫URL
   */
  public ResourceUrl(URL url) {
    this.url = url;
  }

  /**
   * 從URL中擷取輸入流
   */
  @Override
  public InputStream getInputstream() throws Exception {
    URLConnection urlConnection = url.openConnection();
    urlConnection.connect();
    return urlConnection.getInputStream();
  }

}      

當然還需要一個接口,隻定義了一個抽象方法

package cn.thinkinjava.myspring.io;
import java.io.InputStream;

/**
 * 資源定義
 *
 * @author stateis0
 */
public interface Resource {

  /**
   * 擷取輸入流
   */
  InputStream getInputstream() throws Exception;

}      

好了, AbstractBeanDefinitionReader 需要的元素已經有了,但是,很明顯該方法不能實作讀取 BeanDefinition 的任務。那麼我們需要一個類去繼承抽象類,去實作具體的方法, 既然我們是XML 配置檔案讀取,那麼我們就定義一個 XmlBeanDefinitionReader 繼承 AbstractBeanDefinitionReader ,實作一些我們需要的方法, 比如讀取XML 的readrXML, 比如将解析出來的元素注冊到 registry 的 Map 中, 一些解析的細節。我們還是看代碼吧。

XmlBeanDefinitionReader 實作讀取配置檔案并解析成Bean

package cn.thinkinjava.myspring.xml;

import cn.thinkinjava.myspring.AbstractBeanDefinitionReader;
import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.BeanReference;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.io.ResourceLoader;
import java.io.InputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * 解析XML檔案
 *
 * @author stateis0
 */
public class XmlBeanDefinitionReader extends AbstractBeanDefinitionReader {

  /**
   * 構造器,必須包含一個資源加載器
   *
   * @param resourceLoader 資源加載器
   */
  public XmlBeanDefinitionReader(ResourceLoader resourceLoader) {
    super(resourceLoader);
  }

  public void readerXML(String location) throws Exception {
    // 建立一個資源加載器
    ResourceLoader resourceloader = new ResourceLoader();
    // 從資源加載器中擷取輸入流
    InputStream inputstream = resourceloader.getResource(location).getInputstream();
    // 擷取文檔建造者工廠執行個體
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    // 工廠建立文檔建造者
    DocumentBuilder docBuilder = factory.newDocumentBuilder();
    // 文檔建造者解析流 傳回文檔對象
    Document doc = docBuilder.parse(inputstream);
    // 根據給定的文檔對象進行解析,并注冊到bean容器中
    registerBeanDefinitions(doc);
    // 關閉流
    inputstream.close();
  }

  /**
   * 根據給定的文檔對象進行解析,并注冊到bean容器中
   *
   * @param doc 文檔對象
   */
  private void registerBeanDefinitions(Document doc) {
    // 讀取文檔的根元素
    Element root = doc.getDocumentElement();
    // 解析元素的根節點及根節點下的所有子節點并添加進注冊容器
    parseBeanDefinitions(root);
  }

  /**
   * 解析元素的根節點及根節點下的所有子節點并添加進注冊容器
   *
   * @param root XML 檔案根節點
   */
  private void parseBeanDefinitions(Element root) {
    // 讀取根元素的所有子元素
    NodeList nl = root.getChildNodes();
    // 周遊子元素
    for (int i = 0; i < nl.getLength(); i++) {
      // 擷取根元素的給定位置的節點
      Node node = nl.item(i);
      // 類型判斷
      if (node instanceof Element) {
        // 強轉為父類型元素
        Element ele = (Element) node;
        // 解析給給定的節點,包括name,class,property, name, value,ref
        processBeanDefinition(ele);
      }
    }
  }

  /**
   * 解析給給定的節點,包括name,class,property, name, value,ref
   *
   * @param ele XML 解析元素
   */
  private void processBeanDefinition(Element ele) {
    // 擷取給定元素的 name 屬性
    String name = ele.getAttribute("name");
    // 擷取給定元素的 class 屬性
    String className = ele.getAttribute("class");
    // 建立一個bean定義對象
    BeanDefinition beanDefinition = new BeanDefinition();
    // 設定bean 定義對象的 全限定類名
    beanDefinition.setClassname(className);
    // 向 bean 注入配置檔案中的成員變量
    addPropertyValues(ele, beanDefinition);
    // 向注冊容器 添加bean名稱和bean定義
    getRegistry().put(name, beanDefinition);
  }

  /**
   * 添加配置檔案中的屬性元素到bean定義執行個體中
   *
   * @param ele 元素
   * @param beandefinition bean定義 對象
   */
  private void addPropertyValues(Element ele, BeanDefinition beandefinition) {
    // 擷取給定元素的 property 屬性集合
    NodeList propertyNode = ele.getElementsByTagName("property");
    // 循環集合
    for (int i = 0; i < propertyNode.getLength(); i++) {
      // 擷取集合中某個給定位置的節點
      Node node = propertyNode.item(i);
      // 類型判斷
      if (node instanceof Element) {
        // 将節點向下強轉為子元素
        Element propertyEle = (Element) node;
        // 元素對象擷取 name 屬性
        String name = propertyEle.getAttribute("name");
        // 元素對象擷取 value 屬性值
        String value = propertyEle.getAttribute("value");
        // 判斷value不為空
        if (value != null && value.length() > 0) {
          // 向給定的 “bean定義” 執行個體中添加該成員變量
          beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, value));
        } else {
          // 如果為空,則擷取屬性ref
          String ref = propertyEle.getAttribute("ref");
          if (ref == null || ref.length() == 0) {
            // 如果屬性ref為空,則抛出異常
            throw new IllegalArgumentException(
                "Configuration problem: <property> element for property '"
                    + name + "' must specify a ref or value");
          }
          // 如果不為空,測建立一個 “bean的引用” 執行個體,構造參數為名稱,執行個體暫時為空
          BeanReference beanRef = new BeanReference(name);
          // 向給定的 “bean定義” 中添加成員變量
          beandefinition.getPropertyValues().addPropertyValue(new PropertyValue(name, beanRef));
        }
      }
    }
  }

}      

可以說代碼注釋寫的非常詳細,該類方法如下:

public void readerXML(String location) 公開的解析XML的方法,給定一個位置的字元串參數即可。

private void registerBeanDefinitions(Document doc) 給定一個文檔對象,并進行解析。

private void parseBeanDefinitions(Element root) 給定一個根元素,循環解析根元素下所有子元素。

private void processBeanDefinition(Element ele) 給定一個子元素,并對元素進行解析,然後拿着解析出來的資料建立一個 BeanDefinition 對象。并注冊到BeanDefinitionReader 的 Map 容器(該容器存放着解析時的所有Bean)中。

private void addPropertyValues(Element ele, BeanDefinition beandefinition) 給定一個元素,一個 BeanDefinition 對象,解析元素中的 property 元素, 并注入到 BeanDefinition 執行個體中。

一共5步,完成了解析XML檔案的所有操作。 最終的目的是将解析出來的檔案放入到 BeanDefinitionReader 的 Map 容器中。

好了,到這裡,我們已經完成了從XML檔案讀取并解析的步驟,那麼什麼時候放進BeanFactory的容器呢? 剛剛我們隻是放進了 AbstractBeanDefinitionReader 的注冊容器中。是以我們要根據BeanFactory 的設計來實作如何建構成一個真正能用的Bean呢?因為剛才的哪些Bean隻是一些Bean的資訊。沒有我們真正業務需要的Bean。

2. 初始化我們需要的Bean(不是Bean定義)并且實作依賴注入

我們知道Bean定義是不能幹活的,隻是一些Bean的資訊,就好比一個人,BeanDefinition 就相當你在警察局的檔案,但是你人不在警察局,可隻要警察局拿着你的檔案就能找到你。就是這樣一個關系。

那我們就根據BeanFactory的設計來設計一個抽象類 AbstractBeanFactory。

package cn.thinkinjava.myspring.factory;

import cn.thinkinjava.myspring.BeanDefinition;
import java.util.HashMap;

/**
 * 一個抽象類, 實作了 bean 的方法,包含一個map,用于存儲bean 的名字和bean的定義
 *
 * @author stateis0
 */
public abstract class AbstractBeanFactory implements BeanFactory {

  /**
   * 容器
   */
  private HashMap<String, BeanDefinition> map = new HashMap<>();

  /**
   * 根據bean的名稱擷取bean, 如果沒有,則抛出異常 如果有, 則從bean定義對象擷取bean執行個體
   */
  @Override
  public Object getBean(String name) throws Exception {
    BeanDefinition beandefinition = map.get(name);
    if (beandefinition == null) {
      throw new IllegalArgumentException("No bean named " + name + " is defined");
    }
    Object bean = beandefinition.getBean();
    if (bean == null) {
      bean = doCreate(beandefinition);
    }
    return bean;
  }

  /**
   * 注冊 bean定義 的抽象方法實作,這是一個模闆方法, 調用子類方法doCreate,
   */
  @Override
  public void registerBeanDefinition(String name, BeanDefinition beandefinition) throws Exception {
    Object bean = doCreate(beandefinition);
    beandefinition.setBean(bean);
    map.put(name, beandefinition);
  }

  /**
   * 減少一個bean
   */
  abstract Object doCreate(BeanDefinition beandefinition) throws Exception;
}package cn.thinkinjava.myspring.factory;

import cn.thinkinjava.myspring.BeanDefinition;
import cn.thinkinjava.myspring.PropertyValue;
import cn.thinkinjava.myspring.BeanReference;
import java.lang.reflect.Field;


/**
 * 實作自動注入和遞歸注入(spring 的标準實作類 DefaultListableBeanFactory 有 1810 行)
 *
 * @author stateis0
 */
public class AutowireBeanFactory extends AbstractBeanFactory {


  /**
   * 根據bean 定義建立執行個體, 并将執行個體作為key, bean定義作為value存放,并調用 addPropertyValue 方法 為給定的bean的屬性進行注入
   */
  @Override
  protected Object doCreate(BeanDefinition beandefinition) throws Exception {
    Object bean = beandefinition.getBeanclass().newInstance();
    addPropertyValue(bean, beandefinition);
    return bean;
  }

  /**
   * 給定一個bean定義和一個bean執行個體,為給定的bean中的屬性注入執行個體。
   */
  protected void addPropertyValue(Object bean, BeanDefinition beandefinition) throws Exception {
    // 循環給定 bean 的屬性集合
    for (PropertyValue pv : beandefinition.getPropertyValues().getPropertyValues()) {
      // 根據給定屬性名稱擷取 給定的bean中的屬性對象
      Field declaredField = bean.getClass().getDeclaredField(pv.getname());
      // 設定屬性的通路權限
      declaredField.setAccessible(true);
      // 擷取定義的屬性中的對象
      Object value = pv.getvalue();
      // 判斷這個對象是否是 BeanReference 對象
      if (value instanceof BeanReference) {
        // 将屬性對象轉為 BeanReference 對象
        BeanReference beanReference = (BeanReference) value;
        // 調用父類的 AbstractBeanFactory 的 getBean 方法,根據bean引用的名稱擷取執行個體,此處即是遞歸
        value = getBean(beanReference.getName());
      }
      // 反射注入bean的屬性
      declaredField.set(bean, value);
    }

  }

}      

可以看到 doCreate 方法使用了反射建立了一個對象,并且還需要對該對象進行屬性注入,如果屬性是 ref 類型,那麼既是依賴關系,則需要調用 getBean 方法遞歸的去尋找那個Bean(因為最後一個Bean 的屬性肯定是基本類型)。這樣就完成了一次擷取執行個體化Bean操作,并且也實作類依賴注入。

4. 總結

我們通過這些代碼實作了一個簡單的 IOC 依賴注入的功能,也更加了解了 IOC, 以後遇到Spring初始化的問題再也不會手足無措了。直接看源碼就能解決。哈哈

good luck !!!