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