再上一篇文章中,樓主和大家一起分析spring的 IOC 實現,剖析了Spring的原始碼,看的出來,原始碼異常複雜,這是因為Spring的設計者需要考慮到框架的擴充套件性,健壯性,效能等待元素,因此設計的很複雜。樓主在最後也說要實現一個簡單的 IOC,讓我們更加深刻的理解IOC,因此,有了這篇文章。
當然我們是仿照Spring 的 IOC,因此程式碼命名和設計基本是仿照spring的。
我們將分為幾步來編寫簡易 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;
}
複製程式碼
該類實現了介面的2個基本方法,一個是getBean,一個是 registerBeanDefinition, 我們也設計了一個抽象方法供這兩個方法呼叫,將具體邏輯建立邏輯延遲到子類。這是什麼設計模式呢?模板模式。主要還是看 doCreate 方法,就是建立bean 具體方法,所以我們還是需要一個子類, 叫什麼呢? AutowireBeanFactory, 自動注入Bean,這是我們這個標準Bean工廠的工作。看看程式碼吧?
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初始化的問題再也不會手足無措了。直接看原始碼就能解決。哈哈
具體程式碼樓主放在了github上,地址:自己實現的一個簡單IOC,包括依賴注入
good luck !!!