手寫Spring之IOC基於xml動態建立對象

Spring做爲Java Web最爲流行的框架之一,其功能之強大,封裝細節之全面不用過多贅述。使用Spring的方式很簡單,不須要關注細節,把對象的建立和對象之間的關係都交給框架來管理,僅僅作好配置文件和實現具體的業務邏輯便可。能夠說Spring爲咱們在編寫Java Web應用時省去了大量重複的代碼,而且能夠下降對象與對象之間的耦合度。但若只是知其然,而不知其因此然,在編程時也不免會遇到各類問題,我的的水平也難以有所長進。java

所以這篇文章的目的是分享本人對於SpringIOC如何實現控制反轉,以及如何在運行過程當中動態建立對象的理解,算是在漫長的學習過程當中的一個小小的標的。廢話很少說,直接上乾貨!spring

在手寫Spring容器以前,須要作一些前期的準備工做:編程

首先是建立項目,在這裏我爲了後期下載jar包方便,建立的是maven工程,是在JDK1.7的環境下。固然你也能夠建立普通的Java工程,在須要使用第三方的jar包時手動導入。app

clipboard.png

在pom.xml文件中添加jar包依賴路徑,下載所須要的第三方API,本次須要使用dom4j去解析xml配置文件。框架

<dependencies>  
    <dependency>  
      <groupId>dom4j</groupId>  
      <artifactId>dom4j</artifactId>  
      <version>1.6.1</version>  
    </dependency>  
</dependencies>

建立xml配置文件,爲了後面使用方便,在這裏我起名爲:user.xml,放在根目錄下:dom

clipboard.png

配置文件內容以下:maven

<?xml version="1.0" encoding="UTF-8"?>  
<beans>   
    <bean id="user1" class="entity.User" scope="singleton">  
        <property name="id" value="1"></property>  
        <property name="name" value="張三"></property>  
        <property name="password" value="12456"></property>  
    </bean>  
    <bean id="user2" class="entity.User" scope="prototype">  
        <property name="id" value="2"></property>  
        <property name="name" value="李四"></property>  
        <property name="password" value="654321"></property>  
    </bean>  
</beans>

而後根據xml配置文件中的class路徑建立對應的POJO實體類:Useride

clipboard.png

User類中內容以下(爲了節省篇幅,省略setter和getter方法):學習

public class User {  
    private Integer id;  
    private String name;  
    private String password;  
    public User() {  
        System.out.println("無參構造方法執行");  
    }  
    //setters和getters...  
}

主角登場...測試

建立ClassPathXmlApplicationContext類:

clipboard.png

先定義幾個後面要用到的容器,這裏我使用的是Map來存儲對象:

package applicationContext;  

public class ClassPathXmlApplicationContext {  
    /**存儲單例對象容器*/  
    private Map<String, Object> singletonBeanFactory;  
    /**存儲建立類定義對象的容器*/  
    private Map<String, Class<?>> beanDefinationFactory;  
    /**存儲beanElement對象容器*/  
    private Map<String, Element> beanEleMap;  
    /**存儲bean的scope屬性容器*/  
    private Map<String, String> beanScopeMap;  
}

定義有參的構造方法,在構造方法中初始化容器,並調用初始化方法:

/**有參的構造方法,在建立此類實例時須要指定xml文件路徑*/  
public ClassPathXmlApplicationContext(String xmlPath) {  
    //初始化容器  
    singletonBeanFactory = new ConcurrentHashMap<String, Object>();  
    beanDefinationFactory = new ConcurrentHashMap<String, Class<?>>();  
    beanEleMap = new ConcurrentHashMap<String, Element>();  
    beanScopeMap = new ConcurrentHashMap<String, String>();  
    //調用初始化方法  
    init(xmlPath);  
}

init初始化方法內容以下,每一行我都加了詳細的註釋,請直接看代碼:

/** 
 * 初始化方法,在建立ClassPathXmlApplicationContext對象時初始化容器, 
 * 並解析xml配置文件,獲取bean元素,在運行時動態建立對象,併爲對象的屬性賦值, 
 * 最後把對象存放在容器中以供獲取 
 * @param xmlPath 配置文件路徑 
 */  
private void init(String xmlPath) {  
    /* 
     * 使用dom4j技術讀取xml文檔 
     * 首先建立SAXReader對象 
     */  
    SAXReader reader = new SAXReader();  
    try {  
        //獲取讀取xml配置文件的輸入流  
        InputStream is = getClass().getClassLoader().getResourceAsStream(xmlPath);  
        //讀取xml,該操做會返回一個Document對象  
        Document document = reader.read(is);  
        //獲取文檔的根元素  
        Element rootElement = document.getRootElement();  
        //獲取根元素下全部的bean元素,elements方法會返回元素的集合  
        List<Element> beanElements = rootElement.elements("bean");  
        //遍歷元素集合  
        for (Element beanEle : beanElements) {  
            //獲取bean的id值,該值用於做爲key存儲於Map集合中  
            String beanId = beanEle.attributeValue("id");  
            //將beanElement對象存入map中,爲對象設置屬性值時使用  
            beanEleMap.put(beanId, beanEle);  
            //獲取bean的scope值  
            String beanScope = beanEle.attributeValue("scope");  
            //若是beanScope不等於null,將bean的scope值存入map中方便後續使用  
            if(beanScope!=null){  
                beanScopeMap.put(beanId, beanScope);  
            }  
            //獲取bean的class路徑  
            String beanClassPath = beanEle.attributeValue("class");  
            //利用反射技術根據得到的beanClass路徑獲得類定義對象  
            Class<?> cls = Class.forName(beanClassPath);  
            //若是反射獲取的類定義對象不爲null,則放入工廠中方便建立其實例對象  
            if(cls!=null){  
                beanDefinationFactory.put(beanId, cls);  
            }  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}

以上爲建立ClassPathXmlApplicationContext對象時自動啓用的初始化方法,要想獲取對象則須要使用getBean方法,代碼以下:

/** 
 * 根據傳入的bean的id值獲取容器中的對象,類型爲Object 
 */  
public Object getBean(String beanId){  
    //根據傳入beanId獲取類對象  
    Class<?> cls = beanDefinationFactory.get(beanId);  
    //根據id獲取該bean對象的element元素對象  
    Element beanEle = beanEleMap.get(beanId);  
    //獲取存在map中的bean元素的scope屬性值  
    String scope = beanScopeMap.get(beanId);  
    Object obj = null;  
    try {  
        //若是scope等於singleton,建立單例對象  
        if("singleton".equals(scope) || null == scope){  
            //判斷容器中是否已有該對象的實例,若是沒有,建立一個實例對象放到容器中  
            if(singletonBeanFactory.get(beanId)==null){  
                Object instance = cls.newInstance();  
                singletonBeanFactory.put(beanId,instance);  
            }  
            //根據beanId獲取對象  
            obj = singletonBeanFactory.get(beanId);  
        }  
        //若是scope等於prototype,則建立並返回多例對象  
        if("prototype".equals(scope)){  
            obj = cls.newInstance();  
        }  
        setFieldValues(beanId, beanEle, scope, cls, obj);  
        return obj;  
    } catch (InstantiationException e) {  
        e.printStackTrace();  
    } catch (IllegalAccessException e) {  
        e.printStackTrace();  
    }  
    //暫不支持其它類型,若不是以上兩種類型或遭遇異常,返回null  
    return null;  
}  
/** 
 * 此爲重載方法,在根據傳入的bean的id值獲取容器中的對象的同時,還能夠自動轉換類型, 
 * 返回指定的類型,在調用該方法時省去強轉的步驟,傳入時第二個參數爲指定的類型, 
 * 方法實現同上一個方法,只是在返回對象前加了類型強轉 
 */  
public <T>T getBean(String beanId,Class<T> c){  
    return (T)getBean(beanId);  
      
}

在以上的getBean方法中,調用了setFieldValues方法,該方法代碼以下:

/** 
 * 該方法用於爲對象設置成員屬性值 
 * @param beanEle bean所對應的element對象 
 * @param beanId bean元素的id屬性 
 * @param beanScope bean元素的scope屬性 
 * @param cls 類對象 
 * @param obj 要爲其成員屬性賦值的實例對象 
 */  
private void setFieldValues(String beanId,Element beanEle,String beanScope,Class<?> cls,Object obj) {  
    try {  
        //獲取每一個bean元素下的全部property元素,該元素用於給屬性賦值  
        List<Element> propEles = beanEle.elements("property");  
        //若是property元素集合爲null,調用putInMap方法將對象放進Map中  
        if(propEles==null){  
            return;  
        }  
        //遍歷property元素集合  
        for (Element propEle : propEles) {  
            //獲取每一個元素的name屬性值和value屬性值  
            String fieldName = propEle.attributeValue("name");  
            String fieldValue = propEle.attributeValue("value");  
            //利用反射技術根據name屬性值得到類的成員屬性  
            Field field = cls.getDeclaredField(fieldName);  
            //將該屬性設置爲可訪問(防止成員屬性被私有化致使訪問失敗)  
            field.setAccessible(true);  
            //獲取成員屬性的類型名稱,若非字符串類型,則須要作相應轉換  
            String fieldTypeName = field.getType().getName();  
            //判斷該成員屬性是否爲int或Integer類型  
            if("int".equals(fieldTypeName) || "java.lang.Integer".equals(fieldTypeName)){  
                //轉換爲int類型併爲該成員屬性賦值  
                int intFieldValue = Integer.parseInt(fieldValue);  
                field.set(obj, intFieldValue);  
            }  
            //判斷該成員屬性是否爲String類型  
            if("java.lang.String".equals(fieldTypeName)){  
                //爲該成員屬性賦值  
                field.set(obj, fieldValue);  
            }  
            //此處省略其它類型的判斷......道理相同!  
        }  
    } catch (Exception e) {  
        e.printStackTrace();  
    }  
}

以上是獲取單例或多例對象時須要調用的getBean方法的所有內容。當調用者使用完容器以後,天然還須要關閉容器釋放資源,所以還須要有一個destroy方法:

/** 
 * 銷燬方法,用於釋放資源 
 */  
public void destroy(){  
    singletonBeanFactory.clear();  
    singletonBeanFactory = null;  
      
    beanDefinationFactory.clear();  
    beanDefinationFactory = null;  
      
    beanEleMap.clear();  
    beanEleMap = null;  
      
    beanScopeMap.clear();  
    beanScopeMap = null;  
}

至此,ClassPathXmlApplicationContext類中的內容所有完成,能夠寫測試類進行測試:

clipboard.png

測試類內容以下,這裏我就簡單寫main方法進行測試:

public class springIocTest {  
    public static void main(String[] args) {  
        //建立ClassPathXmlApplicationContext對象  
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("user.xml");  
        //使用手動強轉的方式獲取單例的User對象  
        User user1_1 = (User) ctx.getBean("user1");  
        System.out.println("單例user1_1:"+user1_1);  
        //使用傳入類對象的方式獲取單例的User對象  
        User user1_2 = ctx.getBean("user1",User.class);  
        System.out.println("單例user1_2:"+user1_2);  
        //使用手動強轉的方式獲取多例的User對象  
        User user2_1 = (User)ctx.getBean("user2");  
        System.out.println("多例user2_1:"+user2_1);  
        //使用傳入類對象的方式獲取多例的User對象  
        User user2_2 = ctx.getBean("user2",User.class);  
        System.out.println("多例user2_2:"+user2_2);  
    }  
}

控制檯打印輸出結果:

clipboard.png

從控制檯輸出的結果中能夠看到,獲取到了4個對象,其中前兩個爲單例對象,後兩個爲多例對象,兩個單例對象在默認調用Object類中的toString方法是其地址值的hashCode十六進制的映射,其映射值徹底一致,能夠說明是同一個對象。並且建立了4個對象,其無參的構造方法只執行了三次。

若是在User類型加入toString方法:

@Override  
public String toString() {  
    return "User [id=" + id + ", name=" + name + ", password=" + password + "]";  
}

再次運行程序,控制檯輸出結果以下:

clipboard.png

能夠看到對象所定義的屬性值也在建立時成功賦值了。

以上是我近期學習Spring所總結的內容,關於建立多例對象的源碼其實我也沒有找到,目前所寫的只是基於個人思路寫出來的方案,與你們一塊兒分享。因爲我的水平有限,不免會有寫錯或者遺漏的地方,甚至可能會有以偏概全。但這並不重要,正如開篇所說,這只是我學習Java在編程成長路上的一個小小的標的。若是有大牛看到,歡迎留言指正,不勝感激~

相關文章
相關標籤/搜索