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 !!!