自己動手實現(xiàn)一個簡單的 IOC,牛皮??!

Java技術(shù)棧
www.javastack.cn
關(guān)注閱讀更多優(yōu)質(zhì)文章
我們將分為幾步來編寫簡易 IOC,首先設(shè)計組件,再設(shè)計接口,然后關(guān)注實現(xiàn)。
1. 設(shè)計組件。
我們還記得Spring中最重要的有哪些組件嗎?BeanFactory 容器,BeanDefinition Bean的基本數(shù)據(jù)結(jié)構(gòu),當(dāng)然還需要加載Bean的資源加載器。大概最后最重要的就是這幾個組件。
容器用來存放初始化好的Bean,BeanDefinition 就是Bean的基本數(shù)據(jù)結(jié)構(gòu),比如Bean的名稱,Bean的屬性 PropertyValue,Bean的方法,是否延遲加載,依賴關(guān)系等。資源加載器就簡單了,就是一個讀取XML配置文件的類,讀取每個標(biāo)簽并解析。
2. 設(shè)計接口
首先肯定需要一個BeanFactory,就是Bean容器,容器接口至少有2個最簡單的方法,一個是獲取Bean,一個注冊Bean.
/**
?*?需要一個beanFactory?定義ioc?容器的一些行為?比如根據(jù)名稱獲取bean,?比如注冊bean,參數(shù)為bean的名稱,bean的定義
?*
?*?@author?stateis0
?*?@version?1.0.0
?*?@Date?2017/11/30
?*/public?interface?BeanFactory?{
?
??/**
???*?根據(jù)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;}
根據(jù)Bean的名字獲取Bean對象,注冊參數(shù)有2個,一個是Bean的名字,一個是 BeanDefinition 對象。
我們看看該類的詳細:
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;
??}
?
??/**
???*?設(shè)置bean的對象
???*/
??public?void?setBean(Object?bean)?{
????this.bean?=?bean;
??}
?
??/**
???*?獲取bean的Class對象
???*/
??public?Class?getBeanclass()?{
????return?this.beanClass;
??}
?
??/**
???*?通過設(shè)置類名稱反射生成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;
??}
?
??/**
???*?設(shè)置bean的屬性
???*/
??public?void?setPropertyValues(PropertyValues?pv)?{
????this.propertyValues?=?pv;
??}
}
有了基本的 BeanDefinition 數(shù)據(jù)結(jié)構(gòu),還需要一個從XML中讀取并解析為 BeanDefinition 的操作類,首先我們定義一個 BeanDefinitionReader 接口,該接口只是一個標(biāo)識,具體由抽象類去實現(xiàn)一個基本方法和定義一些基本屬性,比如一個讀取時需要存放的注冊容器,還需要一個委托一個資源加載器 ResourceLoader, 用于加載XML文件,并且我們需要設(shè)置該構(gòu)造器必須含有資源加載器,當(dāng)然還有一些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?registry;
?
??/**
???*?資源加載器
???*/
??private?ResourceLoader?resourceLoader;
?
??/**
???*?構(gòu)造器器必須有一個資源加載器,?默認(rèn)插件創(chuàng)建一個map容器
???*
???*?@param?resourceLoader?資源加載器
???*/
??protected?AbstractBeanDefinitionReader(ResourceLoader?resourceLoader)?{
????this.registry?=?new?HashMap<>();
????this.resourceLoader?=?resourceLoader;
??}
?
??/**
???*?獲取容器
???*/
??public?Map?getRegistry()?{
????return?registry;
??}
?
??/**
???*?獲取資源加載器
???*/
??public?ResourceLoader?getResourceLoader()?{
????return?resourceLoader;
??}
??
}
有了這幾個抽象類和接口,我們基本能形成一個雛形,BeanDefinitionReader 用于從XML中讀取配置文件,生成 BeanDefinition 實例,存放在 BeanFactory 容器中,初始化之后,就可以調(diào)用 getBean 方法獲取初始化成功的Bean。形成一個完美的閉環(huán)。
3. 如何實現(xiàn)
剛剛我們說了具體的流程:從XML中讀取配置文件, 解析成 BeanDefinition,最終放進容器。說白了就3步。那么我們就先來設(shè)計第一步。推薦看下《Java 必看的 Spring 知識匯總》,關(guān)注公眾號Java技術(shù)棧獲取更多Spring系列教程。
1. ?從XML中讀取配置文件, 解析成 BeanDefinition
我們剛剛設(shè)計了一個讀取BeanDefinition 的接口 BeanDefinitionReader 和一個實現(xiàn)它的抽象類 AbstractBeanDefinitionReader,抽象了定義了一些簡單的方法,其中由一個委托類-----ResourceLoader, 我們還沒有創(chuàng)建, 該類是資源加載器,根據(jù)給定的路徑來加載資源。
我們可以使用Java 默認(rèn)的類庫 java.net.URL 來實現(xiàn),定義兩個類,一個是包裝了URL的類 ResourceUrl, 一個是依賴 ResourceUrl 的資源加載類。
ResourceUrl 代碼實現(xiàn)
/**
?*?資源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 實現(xiàn)
/**
?*?資源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();
??}
??
}
當(dāng)然還需要一個接口,只定義了一個抽象方法
package?cn.thinkinjava.myspring.io;
import?java.io.InputStream;
/**
?*?資源定義
?*
?*?@author?stateis0
?*/
public?interface?Resource?{
?
??/**
???*?獲取輸入流
???*/
??InputStream?getInputstream()?throws?Exception;
}
好了, AbstractBeanDefinitionReader 需要的元素已經(jīng)有了,但是,很明顯該方法不能實現(xiàn)讀取 BeanDefinition 的任務(wù)。那么我們需要一個類去繼承抽象類,去實現(xiàn)具體的方法, 既然我們是XML 配置文件讀取,那么我們就定義一個 XmlBeanDefinitionReader 繼承 AbstractBeanDefinitionReader ,實現(xiàn)一些我們需要的方法, 比如讀取XML 的readrXML, 比如將解析出來的元素注冊到 registry 的 Map 中, 一些解析的細節(jié)。我們還是看代碼吧。
XmlBeanDefinitionReader ?實現(xiàn)讀取配置文件并解析成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?{
??/**
???*?構(gòu)造器,必須包含一個資源加載器
???*
???*?@param?resourceLoader?資源加載器
???*/
??public?XmlBeanDefinitionReader(ResourceLoader?resourceLoader)?{
????super(resourceLoader);
??}
??public?void?readerXML(String?location)?throws?Exception?{
????//?創(chuàng)建一個資源加載器
????ResourceLoader?resourceloader?=?new?ResourceLoader();
????//?從資源加載器中獲取輸入流
????InputStream?inputstream?=?resourceloader.getResource(location).getInputstream();
????//?獲取文檔建造者工廠實例
????DocumentBuilderFactory?factory?=?DocumentBuilderFactory.newInstance();
????//?工廠創(chuàng)建文檔建造者
????DocumentBuilder?docBuilder?=?factory.newDocumentBuilder();
????//?文檔建造者解析流?返回文檔對象
????Document?doc?=?docBuilder.parse(inputstream);
????//?根據(jù)給定的文檔對象進行解析,并注冊到bean容器中
????registerBeanDefinitions(doc);
????//?關(guān)閉流
????inputstream.close();
??}
??/**
???*?根據(jù)給定的文檔對象進行解析,并注冊到bean容器中
???*
???*?@param?doc?文檔對象
???*/
??private?void?registerBeanDefinitions(Document?doc)?{
????//?讀取文檔的根元素
????Element?root?=?doc.getDocumentElement();
????//?解析元素的根節(jié)點及根節(jié)點下的所有子節(jié)點并添加進注冊容器
????parseBeanDefinitions(root);
??}
??/**
???*?解析元素的根節(jié)點及根節(jié)點下的所有子節(jié)點并添加進注冊容器
???*
???*?@param?root?XML?文件根節(jié)點
???*/
??private?void?parseBeanDefinitions(Element?root)?{
????//?讀取根元素的所有子元素
????NodeList?nl?=?root.getChildNodes();
????//?遍歷子元素
????for?(int?i?=?0;?i???????//?獲取根元素的給定位置的節(jié)點
??????Node?node?=?nl.item(i);
??????//?類型判斷
??????if?(node?instanceof?Element)?{
????????//?強轉(zhuǎn)為父類型元素
????????Element?ele?=?(Element)?node;
????????//?解析給給定的節(jié)點,包括name,class,property,?name,?value,ref
????????processBeanDefinition(ele);
??????}
????}
??}
??/**
???*?解析給給定的節(jié)點,包括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");
????//?創(chuàng)建一個bean定義對象
????BeanDefinition?beanDefinition?=?new?BeanDefinition();
????//?設(shè)置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");
????//?循環(huán)集合
????for?(int?i?=?0;?i???????//?獲取集合中某個給定位置的節(jié)點
??????Node?node?=?propertyNode.item(i);
??????//?類型判斷
??????if?(node?instanceof?Element)?{
????????//?將節(jié)點向下強轉(zhuǎn)為子元素
????????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:??element?for?property?'"
????????????????????+?name?+?"'?must?specify?a?ref?or?value");
??????????}
??????????//?如果不為空,測創(chuàng)建一個?“bean的引用”?實例,構(gòu)造參數(shù)為名稱,實例暫時為空
??????????BeanReference?beanRef?=?new?BeanReference(name);
??????????//?向給定的?“bean定義”?中添加成員變量
??????????beandefinition.getPropertyValues().addPropertyValue(new?PropertyValue(name,?beanRef));
????????}
??????}
????}
??}
}
可以說代碼注釋寫的非常詳細,該類方法如下:
public void readerXML(String location) 公開的解析XML的方法,給定一個位置的字符串參數(shù)即可。
private void registerBeanDefinitions(Document doc) 給定一個文檔對象,并進行解析。
private void parseBeanDefinitions(Element root) 給定一個根元素,循環(huán)解析根元素下所有子元素。
private void processBeanDefinition(Element ele) 給定一個子元素,并對元素進行解析,然后拿著解析出來的數(shù)據(jù)創(chuàng)建一個 BeanDefinition 對象。并注冊到BeanDefinitionReader 的 Map 容器(該容器存放著解析時的所有Bean)中。
private void addPropertyValues(Element ele, BeanDefinition beandefinition) 給定一個元素,一個 BeanDefinition 對象,解析元素中的 ?property 元素, 并注入到 BeanDefinition ?實例中。
一共5步,完成了解析XML文件的所有操作。最終的目的是將解析出來的文件放入到 BeanDefinitionReader 的 Map 容器中。
2. 初始化我們需要的Bean(不是Bean定義)并且實現(xiàn)依賴注入
我們知道Bean定義是不能干活的,只是一些Bean的信息,就好比一個人,BeanDefinition 就相當(dāng)你在公安局的檔案,但是你人不在公安局,可只要公安局拿著你的檔案就能找到你。就是這樣一個關(guān)系。
那我們就根據(jù)BeanFactory的設(shè)計來設(shè)計一個抽象類 AbstractBeanFactory。
package?cn.thinkinjava.myspring.factory;
import?cn.thinkinjava.myspring.BeanDefinition;
import?java.util.HashMap;
/**
?*?一個抽象類,?實現(xiàn)了?bean?的方法,包含一個map,用于存儲bean?的名字和bean的定義
?*
?*?@author?stateis0
?*/
public?abstract?class?AbstractBeanFactory?implements?BeanFactory?{
??/**
???*?容器
???*/
??private?HashMap?map?=?new?HashMap<>();
??/**
???*?根據(jù)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定義?的抽象方法實現(xiàn),這是一個模板方法,?調(diào)用子類方法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;
/**
?*?實現(xiàn)自動注入和遞歸注入(spring?的標(biāo)準(zhǔn)實現(xiàn)類?DefaultListableBeanFactory?有?1810?行)
?*
?*?@author?stateis0
?*/
public?class?AutowireBeanFactory?extends?AbstractBeanFactory?{
??/**
???*?根據(jù)bean?定義創(chuàng)建實例,?并將實例作為key,?bean定義作為value存放,并調(diào)用?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?{
????//?循環(huán)給定?bean?的屬性集合
????for?(PropertyValue?pv?:?beandefinition.getPropertyValues().getPropertyValues())?{
??????//?根據(jù)給定屬性名稱獲取?給定的bean中的屬性對象
??????Field?declaredField?=?bean.getClass().getDeclaredField(pv.getname());
??????//?設(shè)置屬性的訪問權(quán)限
??????declaredField.setAccessible(true);
??????//?獲取定義的屬性中的對象
??????Object?value?=?pv.getvalue();
??????//?判斷這個對象是否是?BeanReference?對象
??????if?(value?instanceof?BeanReference)?{
????????//?將屬性對象轉(zhuǎn)為?BeanReference?對象
????????BeanReference?beanReference?=?(BeanReference)?value;
????????//?調(diào)用父類的?AbstractBeanFactory?的?getBean?方法,根據(jù)bean引用的名稱獲取實例,此處即是遞歸
????????value?=?getBean(beanReference.getName());
??????}
??????//?反射注入bean的屬性
??????declaredField.set(bean,?value);
????}
??}
}
可以看到 doCreate 方法使用了反射創(chuàng)建了一個對象,并且還需要對該對象進行屬性注入,如果屬性是 ref 類型,那么既是依賴關(guān)系,則需要調(diào)用 getBean 方法遞歸的去尋找那個Bean(因為最后一個Bean 的屬性肯定是基本類型)。這樣就完成了一次獲取實例化Bean操作,并且也實現(xiàn)類依賴注入。
4. 總結(jié)
我們通過這些代碼實現(xiàn)了一個簡單的 IOC 依賴注入的功能,也更加了解了 IOC, 以后遇到Spring初始化的問題再也不會手足無措了,直接看源碼就能解決。關(guān)注公眾號Java技術(shù)棧將繼續(xù)分享更多手寫開源框架系列。
good luck ?。?!
作者:莫那一魯?shù)?br>鏈接:https://www.jianshu.com/p/6e25dc62e3a1






關(guān)注Java技術(shù)棧看更多干貨


