博客創建了2年多了,一直沒敢寫點東西,怕技術不夠誤導了別人。2年多后的今天我已經很有信心能夠為需要幫助的人做點微薄的貢獻了。這是我第一次寫博客,先自我介紹一下。本人網名淚滴,一個非常傷心的名字,生活中除了代碼一無所有,平時喜歡看開源框架的源碼,今天也為開源貢獻一份自己的力量。
這次項目叫做IOC框架,是根據spring的IoC的使用風格使用自己的代碼實現。項目的目的不是為了推銷我的框架,只是為了讓目前正在使用IoC和將要使用IoC的小伙伴們對IoC有一個全新的認識,相信淚滴,我不會浪費你們寶貴的時間,不管你是新手還是大牛都會從中有所收獲。由於我有朝九晚五的工作,精力有限,所以本次項目采用連載的方式,盡量保證每周至少2,3次更新,每次更新至少解決一個階段性的問題。廢話我就不多說了,下面開始正題。
IoC框架,估計使用過java,使用過spring的人都不會陌生,它就是一個依賴注入的功能,通俗的說就是可以在項目或者服務啟動使用的時候給我們一次性准備好我們程序中配置好的所需要的對象,並且自動將對象set到需要的對象中。例如A類中的成員變量中有一個B類的引用,那么生成對象的時候會生成一個A的對象和B的對象,並且將B的對象通過A的構造方法或者set方法將B對象設置到A對象中。其實看來完成的功能很簡單就是為了讓我們在代碼中減少使用new關鍵字的次數,讓大多數程序中的new的性能開銷和代碼開銷集中在項目啟動或者服務啟動的時候。在程序的編寫過程中不會一次又一次的寫A a = new A()這種沒有營養的代碼,在程序運行的時候不會因為多次的new對象浪費時間。
注意上面我們提到的IoC的功能我們可以發現幾個很關鍵的地方。第一:該框架產生的動作就是生成對象,第二:生成了對象還要考慮該對象是不是需要依賴別的對象,如果需要要給他set好需要的對象,第三:前兩個動作產生的時機一般就是項目啟動或者服務啟動的時候。這時候我們是不是大腦中已經有一個很簡單的想法了,我們將項目中所需要的生成的對象全部配置在配置文件中,然后在main方法中或者是web中的監聽器Listener調用一個方法去讀取配置文件然后將他們的對象全部生成出來。至於配置文件采用什么格式的配置文件也是需要考慮的地方,由於有些對象需要依賴其他對象,而且依賴的對象個數也不一定,也有可能A依賴B,B又依賴C...這種層級關系栩雅保存起來,放眼配置文件類型,能夠達到保存這種層級關系的文件很明顯就可以用xml文件。至於怎么生成對象又是一個需要考慮的問題,由於我們要生成哪些對象是從配置文件中讀取出來的,都出來后對象的表示肯定都是一個個字符串了,很顯然我們不能使用new去生成對象了。java中什么技術可以使用字符串表示的類生成對象的,顯然Class.forName(className)這個作為反射入口的方法是完全滿足要求的。
經過上面的分析是否對IoC的實現有了一個大概的想法。我們再聯想使用spring框架的IoC的流程,我們看看如下碼:
/**首先加載配置文件並解析放到ApplicationContext中*/ ApplicationContext ac = new FileSystemXmlApplicationContext("applicationContext.xml"); /**從ApplicationContext中get出來id為“beanId”的對象*/ ac.getBean("beanId");
可能有人在說自己在寫web代碼的時候沒有寫這種代碼,但是你們集成spring到web中的是否配置過一個監聽器Listener呢,然后啟動tomcat那樣的服務器的時候是不是會發現日志中或多或少會打印出一些生成對象的日志呢,難道這些代碼他不能放到那個監聽服務器啟動的Listener中去嗎。如果不信我的話可以去看看那個Listener的源碼就知道了。退一萬步講,spring如果以后哪個版本采用了什么高大上的技術,讓你看不到這些代碼了。但是毫無疑問作為我們自己編寫的IoC這種策略是完全沒有任何問題的。
上面分析了這么久,我們或多或少對於IoC框架該怎么實現都有了各自的方案了。下面我們結合spring框架的使用方式開始我們自己的IoC的框架之旅吧。首先spring框架拋開注解方式先不說,我們都是將需要生成的對象以bean的方式配置在xml文件中。說到xml我們就會想到xml格式的驗證是需要dtd或者schema的,spring中xml文件的驗證使用的是schema,由於schema的學習成本比dtd大,dtd又完全可以滿足我們框架的要求,所以我們的IoC選用dtd文件作為校驗xml的文件。結合spring配置文件中涉及到的標簽元素我們定義的dtd文件如下:
<!--指定xml文檔的根元素為beans,beans里面可以有多個子元素bean,仿照spring --> <!ELEMENT beans ( bean* )> <!--指定根元素beans的兩個屬性,一個是延遲加載,一個是自動裝配,默認為后面的值 --> <!ATTLIST beans default-lazy-init (true | false) "false"> <!ATTLIST beans default-autowire (no | byName) "no"> <!-- 指定bean元素的子元素 --> <!ELEMENT bean ( (constructor-arg | property)* )> <!-- 指定bean元素的屬性值 ,這些屬性和spring里面的類似--> <!ATTLIST bean id CDATA #REQUIRED> <!ATTLIST bean class CDATA #REQUIRED> <!ATTLIST bean lazy-init (true | false | default) "default"> <!ATTLIST bean singleton (true | false) "true"> <!ATTLIST bean autowire (no | byName | default) "default"> <!-- 聲明constructor-arg子元素 --> <!ELEMENT constructor-arg ( (ref | value ) )> <!-- 聲明property元素的子元素 --> <!ELEMENT property ( (ref | value | collection )? )> <!-- 指定collection元素的子元素 --> <!ELEMENT collection ( (ref | value)+ )> <!--聲明collection的屬性 --> <!ATTLIST collection type CDATA #REQUIRED> <!-- 聲明property的屬性 --> <!ATTLIST property name CDATA #REQUIRED> <!-- 聲明property的屬性 --> <!ATTLIST value type CDATA #REQUIRED> <!-- 聲明ref元素 --> <!ELEMENT ref EMPTY> <!-- 聲明ref的屬性 --> <!ATTLIST ref bean CDATA #REQUIRED> <!-- 聲明value元素 --> <!ELEMENT value (#PCDATA)>
對於上面的dtd有了詳細的注釋,如果還是覺得看的吃力,小伙伴們可以自己網上找些dtd的資料話半小時完全可以輕松搞定。這里我們的重點不在這里。至於上面的dtd所校驗下的xml文件的格式是個什么樣子我們可以聯想下spring的applicationContext.xml中的內容就能知道了。這邊和那個大同小異。為了方便下面代碼的講解,我們說下這次項目的包結構
其中項目的根包為com.tear.ioc,bean包下面放一些項目需要使用的bean,dtd下放dtd文件,xml包下放處理xml文件的相關包或者類。下面的講解全部是以這個包為基礎進行的。
dtd文件已經有了,但是我們怎么讓這個dtd去約束xml呢?又怎么解析xml呢,原本的想法我的這個IoC框架不想去依賴任何jdk以外的包的,但是后來發現jdk自帶的dom和sax解析xml用起來很繁瑣,無奈下引用了一個目前使用比較多的dom4j.jar包,我們將獲取xml文件的dom4j里面的document對象的代碼放在xml.document包下:
下面我們定義一個Document對象的持有接口DocumentHolder
package com.tear.ioc.bean.xml.document; import org.dom4j.Document; public interface DocumentHolder { /** * 根據xml文件的路徑得到dom4j里面的Document對象 * @param filePath * @return */ public Document getDocument(String filePath); }
持有接口的實現類XmlDocumentHolder如下
package com.tear.ioc.bean.xml.document; import java.io.File; import java.util.HashMap; import java.util.Map; import org.dom4j.Document; import org.dom4j.io.SAXReader; public class XmlDocumentHolder implements DocumentHolder { /** * 由於可能配置多個配置文件所以定義一個Map類型的成員變量用配置文件的路徑關聯他們的Document對象 * Map的實際類型定義成了HashMap */ private Map<String, Document> documents = new HashMap<String, Document>(); /** * 根據xml文件的路徑得到dom4j里面的Document對象 * @param filePath * @return */ @Override public Document getDocument(String filePath) { /** * 通過xml文件的路徑獲取出Map里保存的Document對象 */ Document doc = this.documents.get(filePath); /** * 如果根據xml文件的路徑從Map中取出的Document對象為空,則調用本類里面定義的 * readDocument方法獲得該路徑所對應文件的Document對象后,在將路徑和Document * 對象這樣一對信息保存到Map中去 */ if (doc == null) { //使用SAXReader來讀取xml文件 this.documents.put(filePath, this.readDocument(filePath)); } /** * 返回Map中該xml文檔路徑所對應的Document對象 */ return this.documents.get(filePath); } /** * 根據文件的路徑讀取出Document對象,該方法是准備被下面的getDocument方法調用的 * 所以定義成了private * @param filePath * @return */ private Document readDocument(String filePath) { try { /** * new一個帶dtd驗證的SaxReader對象 */ SAXReader reader = new SAXReader(true); /** * 設置用來驗證的dtd的輸入源 */ reader.setEntityResolver(new XmlEntityResolver()); /** * 根據xml的路徑讀取出Document對象 */ File xmlFile = new File(filePath); Document document = reader.read(xmlFile); return document; } catch (Exception e) { e.printStackTrace(); } return null; } }
使用指定dtd驗證xml的resolver類XmlEntityResolver如下
package com.tear.ioc.bean.xml.document; import java.io.IOException; import java.io.InputStream; import org.xml.sax.EntityResolver; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * 自定義的XmlEntityResolver實現dom4j里的EntityResolver接口並實現里面的 * resolveEntity方法,來獲得一個dtd的輸入源 * @author rongdi */ public class XmlEntityResolver implements EntityResolver { @Override public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { /** * 如果自己寫的xml配置文件中引入dtd的時候publicId與"-//RONGDI//DTD BEAN//CN"相同 * 並且systemId與"http://www.cnblogs.com/rongdi/beans.dtd"相同,就從本地的相對項目的路徑 * 尋找dtd,返回一個dtd的輸入源,若果找不到該dtd就會嘗試到對應的網址上尋找 */ if ("-//RONGDI//DTD BEAN//CN".equals(publicId)&&"http://www.cnblogs.com/rongdi/beans.dtd".equals(systemId)) { InputStream stream = this.getClass(). getResourceAsStream("/com/tear/ioc/bean/dtd/beans.dtd"); return new InputSource(stream); } return null; } }
注釋在代碼中寫的很詳細了這里就不多廢話了。下面使用jUnit測試一下上面獲取document的代碼
在test包下建立包com.tear.ioc.xml.document包和resources包,resources.document包存放需要使用的xml文件等資源
resources.document包下測試獲取document對象的正確文件xmlDocumentHolder.xml如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//RONGDI//DTD BEAN//CN" "http://www.cnblogs.com/rongdi/beans.dtd"> <beans> <bean id="test" class="test"></bean> </beans>
xml經不過dtd驗證的文件xmlDocumentHolder2.xml如下
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//RONGDI//DTD BEAN//CN" "http://www.cnblogs.com/rongdi/beans.dtd"> <beans> <bean id="test" ></bean> </beans>
xml中publicId寫錯了的xml文件xmlDocumentHolder3.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//ABABAB//DTD BEAN//CN" "http://www.cnblogs.com/rongdi/beans.dtd"> <beans> <bean id="test" class="test"></bean> </beans>
上面現階段最主要關注的是DOCTYPE部分,下面的beans和bean部分屬性中的內容對於現階段隨便寫什么都可以。
單元測試代碼如下
package com.tear.ioc.xml.document; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.tear.ioc.bean.xml.document.XmlDocumentHolder; public class XmlDocumentHolderTest { private XmlDocumentHolder xmlHolder; @Before public void setUp() throws Exception { xmlHolder = new XmlDocumentHolder(); } @After public void tearDown() throws Exception { xmlHolder = null; } //測試正常情況 @Test public void testGetDocument1() { String filePath = "test/resources/document/xmlDocumentHolder.xml"; //獲得Document對象 Document doc1 = xmlHolder.getDocument(filePath); //看是否為空,為空測試失敗 assertNotNull(doc1); //得到xml文檔根元素 Element root = doc1.getRootElement(); //判斷根元素是否為beans,不是beans測試失敗 assertEquals(root.getName(), "beans"); //再獲取一次Document對象,看是否一致 Document doc2 = xmlHolder.getDocument(filePath); System.out.println(doc1); System.out.println(doc1); assertEquals(doc1, doc2); } //測試一個讀取DTD驗證不合格的xml文件看是否拋出異常 @Test(expected = DocumentException.class) public void testGetDocument2(){ /* * 定義一個dtd驗證不合格的xml文件,該xml文件的bean元素id和class是必須, * 但是少了一個class應該拋出異常 */ String filePath = "test/resources/document/xmlDocumentHolder2.xml"; //獲得Document對象 Document doc = xmlHolder.getDocument(filePath); } //測試一個讀取不到DTD的情況(DTD里面的publicId或systemId寫錯了無法再本地獲取dtd就會 //嘗試到網上下載,但是自定義的網站根本不存在就會報錯了) @Test(expected = DocumentException.class) public void testGetDocument3() throws DocumentException{ /* * 定義一個dtd驗證不合格的xml文件,該xml文件的bean元素id和class是必須, * 但是少了一個class應該拋出異常 */ String filePath = "test/resources/document/xmlDocumentHolder3.xml"; //獲得Document對象 Document doc = xmlHolder.getDocument(filePath); } }
好了對於IoC的開始部分,定義dtd即如何使用dtd校驗xml的代碼部分已經結束了。下次再見。
源碼百度雲地址http://pan.baidu.com/s/1sjHMT33 如果有問題可以留言我會在看到后第一時間回復