本身動手編寫IOC框架(一)

  博客建立了2年多了,一直沒敢寫點東西,怕技術不夠誤導了別人。2年多後的今天我已經頗有信心可以爲須要幫助的人作點微薄的貢獻了。這是我第一次寫博客,先自我介紹一下。本人網名淚滴,一個很是傷心的名字,生活中除了代碼一無全部,平時喜歡看開源框架的源碼,今天也爲開源貢獻一份本身的力量。java

  此次項目叫作IOC框架,是根據spring的IoC的使用風格使用本身的代碼實現。項目的目的不是爲了推銷個人框架,只是爲了讓目前正在使用IoC和將要使用IoC的小夥伴們對IoC有一個全新的認識,相信淚滴,我不會浪費大家寶貴的時間,無論你是新手仍是大牛都會從中有所收穫。因爲我有朝九晚五的工做,精力有限,因此本次項目採用連載的方式,儘可能保證每週至少2,3次更新,每次更新至少解決一個階段性的問題。廢話我就很少說了,下面開始正題。web

  IoC框架,估計使用過java,使用過spring的人都不會陌生,它就是一個依賴注入的功能,通俗的說就是能夠在項目或者服務啓動使用的時候給咱們一次性準備好咱們程序中配置好的所須要的對象,而且自動將對象set到須要的對象中。例如A類中的成員變量中有一個B類的引用,那麼生成對象的時候會生成一個A的對象和B的對象,而且將B的對象經過A的構造方法或者set方法將B對象設置到A對象中。其實看來完成的功能很簡單就是爲了讓咱們在代碼中減小使用new關鍵字的次數,讓大多數程序中的new的性能開銷和代碼開銷集中在項目啓動或者服務啓動的時候。在程序的編寫過程當中不會一次又一次的寫A a = new A()這種沒有養分的代碼,在程序運行的時候不會由於屢次的new對象浪費時間。spring

  注意上面咱們提到的IoC的功能咱們能夠發現幾個很關鍵的地方。第一:該框架產生的動做就是生成對象,第二:生成了對象還要考慮該對象是否是須要依賴別的對象,若是須要要給他set好須要的對象,第三:前兩個動做產生的時機通常就是項目啓動或者服務啓動的時候。這時候咱們是否是大腦中已經有一個很簡單的想法了,咱們將項目中所須要的生成的對象所有配置在配置文件中,而後在main方法中或者是web中的監聽器Listener調用一個方法去讀取配置文件而後將他們的對象所有生成出來。至於配置文件採用什麼格式的配置文件也是須要考慮的地方,因爲有些對象須要依賴其餘對象,並且依賴的對象個數也不必定,也有可能A依賴B,B又依賴C...這種層級關係栩雅保存起來,放眼配置文件類型,可以達到保存這種層級關係的文件很明顯就能夠用xml文件。至於怎麼生成對象又是一個須要考慮的問題,因爲咱們要生成哪些對象是從配置文件中讀取出來的,都出來後對象的表示確定都是一個個字符串了,很顯然咱們不能使用new去生成對象了。java中什麼技術可使用字符串表示的類生成對象的,顯然Class.forName(className)這個做爲反射入口的方法是徹底知足要求的。tomcat

  通過上面的分析是否對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這種策略是徹底沒有任何問題的。app

  上面分析了這麼久,咱們或多或少對於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中的內容就能知道了。這邊和那個大同小異。爲了方便下面代碼的講解,咱們說下此次項目的包結構dom

其中項目的根包爲com.tear.ioc,bean包下面放一些項目須要使用的bean,dtd下放dtd文件,xml包下放處理xml文件的相關包或者類。下面的講解所有是以這個包爲基礎進行的。ide

  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 若是有問題能夠留言我會在看到後第一時間回覆

相關文章
相關標籤/搜索