Spring作為Java Web最為流行的框架之一,其功能之強大,封裝細(xì)節(jié)之全面不用過多贅述。使用Spring的方式很簡單,不需要關(guān)注細(xì)節(jié),把對象的創(chuàng)建和對象之間的關(guān)系都交給框架來管理,僅僅做好配置文件和實現(xiàn)具體的業(yè)務(wù)邏輯即可??梢哉fSpring為我們在編寫Java Web應(yīng)用時省去了大量重復(fù)的代碼,并且可以降低對象與對象之間的耦合度。但若只是知其然,而不知其所以然,在編程時也難免會遇到各種問題,個人的水平也難以有所長進。
因此這篇文章的目的是分享本人對于SpringIOC如何實現(xiàn)控制反轉(zhuǎn),以及如何在運行過程中動態(tài)創(chuàng)建對象的理解,算是在漫長的學(xué)習(xí)過程中的一個小小的標(biāo)的。廢話不多說,直接上干貨!
在手寫Spring容器之前,需要做一些前期的準(zhǔn)備工作:
首先是創(chuàng)建項目,在這里我為了后期下載jar包方便,創(chuàng)建的是maven工程,是在JDK1.7的環(huán)境下。當(dāng)然你也可以創(chuàng)建普通的Java工程,在需要使用第三方的jar包時手動導(dǎo)入。
在pom.xml文件中添加jar包依賴路徑,下載所需要的第三方API,本次需要使用dom4j去解析xml配置文件。
dom4j dom4j 1.6.1
創(chuàng)建xml配置文件,為了后面使用方便,在這里我起名為:user.xml,放在根目錄下:
配置文件內(nèi)容如下:
然后根據(jù)xml配置文件中的class路徑創(chuàng)建對應(yīng)的POJO實體類:User
User類中內(nèi)容如下(為了節(jié)省篇幅,省略setter和getter方法):
public class User { private Integer id; private String name; private String password; public User() { System.out.println("無參構(gòu)造方法執(zhí)行"); } //setters和getters... }
主角登場...
創(chuàng)建ClassPathXmlApplicationContext類:
先定義幾個后面要用到的容器,這里我使用的是Map來存儲對象:
package applicationContext; public class ClassPathXmlApplicationContext { /**存儲單例對象容器*/ private MapsingletonBeanFactory; /**存儲創(chuàng)建類定義對象的容器*/ private Map > beanDefinationFactory; /**存儲beanElement對象容器*/ private Map beanEleMap; /**存儲bean的scope屬性容器*/ private Map beanScopeMap; }
定義有參的構(gòu)造方法,在構(gòu)造方法中初始化容器,并調(diào)用初始化方法:
/**有參的構(gòu)造方法,在創(chuàng)建此類實例時需要指定xml文件路徑*/ public ClassPathXmlApplicationContext(String xmlPath) { //初始化容器 singletonBeanFactory = new ConcurrentHashMap(); beanDefinationFactory = new ConcurrentHashMap >(); beanEleMap = new ConcurrentHashMap (); beanScopeMap = new ConcurrentHashMap (); //調(diào)用初始化方法 init(xmlPath); }
init初始化方法內(nèi)容如下,每一行我都加了詳細(xì)的注釋,請直接看代碼:
/** * 初始化方法,在創(chuàng)建ClassPathXmlApplicationContext對象時初始化容器, * 并解析xml配置文件,獲取bean元素,在運行時動態(tài)創(chuàng)建對象,并為對象的屬性賦值, * 最后把對象存放在容器中以供獲取 * @param xmlPath 配置文件路徑 */ private void init(String xmlPath) { /* * 使用dom4j技術(shù)讀取xml文檔 * 首先創(chuàng)建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方法會返回元素的集合 ListbeanElements = rootElement.elements("bean"); //遍歷元素集合 for (Element beanEle : beanElements) { //獲取bean的id值,該值用于作為key存儲于Map集合中 String beanId = beanEle.attributeValue("id"); //將beanElement對象存入map中,為對象設(shè)置屬性值時使用 beanEleMap.put(beanId, beanEle); //獲取bean的scope值 String beanScope = beanEle.attributeValue("scope"); //如果beanScope不等于null,將bean的scope值存入map中方便后續(xù)使用 if(beanScope!=null){ beanScopeMap.put(beanId, beanScope); } //獲取bean的class路徑 String beanClassPath = beanEle.attributeValue("class"); //利用反射技術(shù)根據(jù)獲得的beanClass路徑得到類定義對象 Class> cls = Class.forName(beanClassPath); //如果反射獲取的類定義對象不為null,則放入工廠中方便創(chuàng)建其實例對象 if(cls!=null){ beanDefinationFactory.put(beanId, cls); } } } catch (Exception e) { e.printStackTrace(); } }
以上為創(chuàng)建ClassPathXmlApplicationContext對象時自動啟用的初始化方法,要想獲取對象則需要使用getBean方法,代碼如下:
/** * 根據(jù)傳入的bean的id值獲取容器中的對象,類型為Object */ public Object getBean(String beanId){ //根據(jù)傳入beanId獲取類對象 Class> cls = beanDefinationFactory.get(beanId); //根據(jù)id獲取該bean對象的element元素對象 Element beanEle = beanEleMap.get(beanId); //獲取存在map中的bean元素的scope屬性值 String scope = beanScopeMap.get(beanId); Object obj = null; try { //如果scope等于singleton,創(chuàng)建單例對象 if("singleton".equals(scope) || null == scope){ //判斷容器中是否已有該對象的實例,如果沒有,創(chuàng)建一個實例對象放到容器中 if(singletonBeanFactory.get(beanId)==null){ Object instance = cls.newInstance(); singletonBeanFactory.put(beanId,instance); } //根據(jù)beanId獲取對象 obj = singletonBeanFactory.get(beanId); } //如果scope等于prototype,則創(chuàng)建并返回多例對象 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; } /** * 此為重載方法,在根據(jù)傳入的bean的id值獲取容器中的對象的同時,還可以自動轉(zhuǎn)換類型, * 返回指定的類型,在調(diào)用該方法時省去強轉(zhuǎn)的步驟,傳入時第二個參數(shù)為指定的類型, * 方法實現(xiàn)同上一個方法,只是在返回對象前加了類型強轉(zhuǎn) */ publicT getBean(String beanId,Class c){ return (T)getBean(beanId); }
在以上的getBean方法中,調(diào)用了setFieldValues方法,該方法代碼如下:
/** * 該方法用于為對象設(shè)置成員屬性值 * @param beanEle bean所對應(yīng)的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元素,該元素用于給屬性賦值 ListpropEles = beanEle.elements("property"); //如果property元素集合為null,調(diào)用putInMap方法將對象放進Map中 if(propEles==null){ return; } //遍歷property元素集合 for (Element propEle : propEles) { //獲取每個元素的name屬性值和value屬性值 String fieldName = propEle.attributeValue("name"); String fieldValue = propEle.attributeValue("value"); //利用反射技術(shù)根據(jù)name屬性值獲得類的成員屬性 Field field = cls.getDeclaredField(fieldName); //將該屬性設(shè)置為可訪問(防止成員屬性被私有化導(dǎo)致訪問失敗) field.setAccessible(true); //獲取成員屬性的類型名稱,若非字符串類型,則需要做相應(yīng)轉(zhuǎn)換 String fieldTypeName = field.getType().getName(); //判斷該成員屬性是否為int或Integer類型 if("int".equals(fieldTypeName) || "java.lang.Integer".equals(fieldTypeName)){ //轉(zhuǎn)換為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(); } }
以上是獲取單例或多例對象時需要調(diào)用的getBean方法的全部內(nèi)容。當(dāng)調(diào)用者使用完容器之后,自然還需要關(guān)閉容器釋放資源,因此還需要有一個destroy方法:
/** * 銷毀方法,用于釋放資源 */ public void destroy(){ singletonBeanFactory.clear(); singletonBeanFactory = null; beanDefinationFactory.clear(); beanDefinationFactory = null; beanEleMap.clear(); beanEleMap = null; beanScopeMap.clear(); beanScopeMap = null; }
至此,ClassPathXmlApplicationContext類中的內(nèi)容全部完成,可以寫測試類進行測試:
測試類內(nèi)容如下,這里我就簡單寫main方法進行測試:
public class springIocTest { public static void main(String[] args) { //創(chuàng)建ClassPathXmlApplicationContext對象 ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("user.xml"); //使用手動強轉(zhuǎn)的方式獲取單例的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); //使用手動強轉(zhuǎn)的方式獲取多例的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); } }
控制臺打印輸出結(jié)果:
從控制臺輸出的結(jié)果中可以看到,獲取到了4個對象,其中前兩個為單例對象,后兩個為多例對象,兩個單例對象在默認(rèn)調(diào)用Object類中的toString方法是其地址值的hashCode十六進制的映射,其映射值完全一致,可以說明是同一個對象。而且創(chuàng)建了4個對象,其無參的構(gòu)造方法只執(zhí)行了三次。
如果在User類型加入toString方法:
@Override public String toString() { return "User [id=" + id + ", name=" + name + ", password=" + password + "]"; }
再次運行程序,控制臺輸出結(jié)果如下:
可以看到對象所定義的屬性值也在創(chuàng)建時成功賦值了。
以上是我近期學(xué)習(xí)Spring所總結(jié)的內(nèi)容,關(guān)于創(chuàng)建多例對象的源碼其實我也沒有找到,目前所寫的只是基于我的思路寫出來的方案,與大家一起分享。由于個人水平有限,難免會有寫錯或者遺漏的地方,甚至可能會有以偏概全。但這并不重要,正如開篇所說,這只是我學(xué)習(xí)Java在編程成長路上的一個小小的標(biāo)的。如果有大??吹剑瑲g迎留言指正,不勝感激~
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/68377.html
摘要:如感興趣,可移步手寫之基于動態(tài)創(chuàng)建對象手寫之基于注解動態(tài)創(chuàng)建對象今天將詳細(xì)介紹如何手寫依賴注入,在運行過程中如何動態(tài)地為對象的屬性賦值。完成后在中會有相關(guān)的包出現(xiàn)進行注入前需要創(chuàng)建工廠,在運行時從工廠中取出對象為屬性賦值。 前兩篇文章介紹了關(guān)于手寫Spring IOC控制反轉(zhuǎn),由Spring工廠在運行過程中動態(tài)地創(chuàng)建對象的兩種方式。如感興趣,可移步: 手寫Spring之IOC基于xml...
摘要:上一篇博客介紹了如何基于配置文件在運行時創(chuàng)建實例對象,這篇博客將介紹基于注解方式怎樣實現(xiàn)對象的創(chuàng)建。方便測試,該類型分別創(chuàng)建兩個單例和多例的類型。注意這種為對象注入屬性值的方式耦合度較高,可根據(jù)情況使用。 上一篇博客介紹了如何基于xml配置文件在運行時創(chuàng)建實例對象,這篇博客將介紹基于注解方式怎樣實現(xiàn)對象的創(chuàng)建。 廢話不多說,直接上代碼。 首先還是創(chuàng)建項目,由于這次不需要使用第三方的AP...
摘要:入門和學(xué)習(xí)筆記概述框架的核心有兩個容器作為超級大工廠,負(fù)責(zé)管理創(chuàng)建所有的對象,這些對象被稱為。中的一些術(shù)語切面切面組織多個,放在切面中定義。 Spring入門IOC和AOP學(xué)習(xí)筆記 概述 Spring框架的核心有兩個: Spring容器作為超級大工廠,負(fù)責(zé)管理、創(chuàng)建所有的Java對象,這些Java對象被稱為Bean。 Spring容器管理容器中Bean之間的依賴關(guān)系,使用一種叫做依賴...
摘要:你都是如何回答面試官的問題的我不知道,我一般會通過手寫一個來加深自己的印象。如今,已然成為了一個生態(tài)。運行階段主要是完成容器啟動以后,完成用戶請求的內(nèi)部調(diào)度,并返回響應(yīng)結(jié)果。因此,要先寫一個針對類名首字母處理的工具方法。 引言 幾乎每個面試的程序員都會碰到Spring相關(guān)的面試問題,或淺或深。你都是如何回答面試官的問題的?——我不知道,我一般會通過手寫一個Spring來加深自己的印象。...
摘要:在上文中,我實現(xiàn)了一個很簡單的和容器。比如,我們所熟悉的就是在這里將切面邏輯織入相關(guān)中的。初始化的工作算是結(jié)束了,此時處于就緒狀態(tài),等待外部程序的調(diào)用。其中動態(tài)代理只能代理實現(xiàn)了接口的對象,而動態(tài)代理則無此限制。 1. 背景 本文承接上文,來繼續(xù)說說 IOC 和 AOP 的仿寫。在上文中,我實現(xiàn)了一個很簡單的 IOC 和 AOP 容器。上文實現(xiàn)的 IOC 和 AOP 功能很單一,且 I...
閱讀 2812·2021-09-26 10:19
閱讀 2207·2021-09-24 10:27
閱讀 2599·2021-09-01 10:42
閱讀 2371·2019-08-29 16:09
閱讀 2556·2019-08-29 15:17
閱讀 1508·2019-08-29 15:09
閱讀 694·2019-08-29 11:14
閱讀 2385·2019-08-26 13:25