崔行臣
(山東廣播電視大學(xué)現(xiàn)代教育技術(shù)中心,山東 濟南 250014)
代理軟件設(shè)計模式及其應(yīng)用研究
崔行臣
(山東廣播電視大學(xué)現(xiàn)代教育技術(shù)中心,山東 濟南 250014)
代理設(shè)計模式是軟件開發(fā)中使用最廣泛的軟件設(shè)計模式之一。本文首先介紹了軟件設(shè)計模式的思想、應(yīng)用場合和實現(xiàn)方式,然后對java中的動態(tài)代理進行了深入研究,最后探討了動態(tài)代理設(shè)計模式在Struts、AOP、Hibernate等方面的應(yīng)用。實踐證明,代理設(shè)計模式在軟件架構(gòu)開發(fā)中體現(xiàn)了責(zé)任清晰,高擴展和智能化等優(yōu)點。
代理設(shè)計模式;動態(tài)代理;AOP
軟件設(shè)計模式是從許多優(yōu)秀的軟件系統(tǒng)中總結(jié)出的成功的可復(fù)用的設(shè)計方案。設(shè)計模式依據(jù)其工作目的可分為創(chuàng)建型(Creational)、結(jié)構(gòu)型(Structural)、和行為型(Behavioral)三種。代理設(shè)計模式屬于結(jié)構(gòu)型軟件設(shè)計模式,代理模式給某一個對象提供一個代理對象,讓代理對象控制某對象的訪問,被代理的對象可以是遠程的對象、創(chuàng)建開銷大的對象或需要安全控制的對象。
Java動態(tài)代理機制的出現(xiàn),使得 Java開發(fā)人員不用手工編寫代理類,只要簡單地指定一組接口及委托類對象,便能動態(tài)地獲得代理類。代理類會負責(zé)將所有的方法調(diào)用分派到委托對象上反射執(zhí)行,在分派執(zhí)行的過程中,開發(fā)人員還可以按需調(diào)整委托類對象及其功能,這是一套非常靈活有彈性的代理框架。
本文首先從代理模式的設(shè)計思想分析開始,對代理模式的使用場景進行了總結(jié),然后對Java動態(tài)代理的運行機制和特點進行了分析,最后對動態(tài)代理模式在 struts2、Spring AOP、Hibernate和數(shù)據(jù)源方面的應(yīng)用進行了深入探討。
代理軟件模式定義:provide a surrogate or placeholder for another object to control access to it.為其他對象提供一種代理以控制對這個對象的訪問。
代理模式是一種應(yīng)用非常廣泛的設(shè)計模式,設(shè)計思想就是為某一個對象的訪問提供一個代理對象,而不是直接去控制這個對象。在某些情況下,客戶端代碼不想或不能夠直接調(diào)用被調(diào)用者,此時我們就可返回該對象的代理(Proxy)。在這種設(shè)計方式下,系統(tǒng)會為某個對象提供一個代理對象,并由代理對象控制對源對象的引用,代理對象可以在客戶和目標(biāo)對象之間起到中介的作用。對客戶端而言,它不能也無須分辨出代理對象與真實對象的區(qū)別。客戶端代碼并不知道真正的被代理對象,客戶端代碼面向接口編程,它僅僅持有一個被代理對象的接口。
抽象主題角色(Subject):聲明真實對象和代理對象的共同接口,是一個最普通的業(yè)務(wù)類型定義。
代理主題角色(Proxysubject):也叫代理類。它負責(zé)對真實角色的應(yīng)用,把所有抽象主題類定義的方法限制委托給真實主題角色實現(xiàn),并且在真實主題角色處理完畢前后做預(yù)處理和善后處理工作。
具體主題角色(Realsubject):也叫被代理角色,代理角色所代表的真實對象,即業(yè)務(wù)的具體執(zhí)行者[1]。
代理各角色及代理模式架構(gòu)圖如圖1所示:
圖1 代理設(shè)計模式
根據(jù)使用目的或使用場景來劃分,代理可以分為以下幾種:
(1)遠程(Remote)代理:遠程方法調(diào)用,為一個位于不同的地址空間的對象提供一個局域代表對象[1]。
(2)虛擬(Virtual)代理:目標(biāo)對象只在需要時才會被真正創(chuàng)建,此時目標(biāo)對象通常為資源消耗較大的對象,可以避免被代理對象較多而引起初始化緩慢的問題,能顯著提高系統(tǒng)的性能。代理對象和被代理對象實現(xiàn)同一的接口。當(dāng)程序需要使用目標(biāo)對象時并不是直接返回objectA對象,而是先返回proxyObjectA對象,當(dāng)控制邏輯真正調(diào)用被調(diào)用對象的某一方法時,才真正創(chuàng)建被代理的objectA對象。
(3)保護(Protect or Access)代理:控制對一個對象的訪問,如果有需要,可以給不同的用戶提供不同級別的使用權(quán)限。
(4)智能引用代理:當(dāng)目標(biāo)對象被引用時,提供一些額外操作用來增強目標(biāo)對象的功能。這種增強的本質(zhì)就是對目標(biāo)對象的方法進行攔截和過濾。借助于Java提供的Proxy和InvocationHandler,可以實現(xiàn)在運行時生成動態(tài)代理的功能,而動態(tài)代理對象就可作為目標(biāo)對象使用,而且增強了目標(biāo)對象的功能。
(5)Cache代理:為某一目標(biāo)操作的結(jié)果提供臨時的存儲空間,以便多個客戶端可以共享這些結(jié)果。
(6)防火墻代理:保護目標(biāo),不讓惡意用戶接近。
代理在實現(xiàn)方式上可以分為靜態(tài)代理和動態(tài)代理。靜態(tài)代理在源碼級實現(xiàn),而動態(tài)代理在運行時實現(xiàn)。
JVM生成的動態(tài)代理類必須實現(xiàn)一個或多個接口,所以JVM生成的動態(tài)類只能用作具有相同接口的目標(biāo)類的代理,如果要為一個沒有實現(xiàn)接口的類生成動態(tài)代理類,可以使用CGLIB庫,CGLIB庫可以動態(tài)生成一個類的子類用作該類的代理。Java語言通過java.lang.reflect庫中,提供3個類來支持代理模式[2]:Proxy,InvocationHandle和Method。
(1)InvocationHandler:該接口中僅定義了一個方法Object:invoke(Object obj,Method method,Object[]args) throws Throwable。在實際使用時,第一個參數(shù)obj一般是指代理類,method是被代理的方法,如圖2中的request(),args為該方法的參數(shù)數(shù)組。這個抽象方法在代理類中動態(tài)實現(xiàn)。
(2)Proxy:該類即為動態(tài)代理類,作用類似于圖1的ProxySubject,其中主要包含以下內(nèi)容:
Protected Proxy(InvocationHandler h):唯一的構(gòu)造函數(shù),用于給內(nèi)部的成員變量h賦值。
Static Class getProxyClass(ClassLoader loader,Class[]interfaces):獲得一個代理類,其中 loader是類裝載器,interfaces是真實類所擁有的全部接口的數(shù)組。
Static ObjectnewProxyInstance(ClassLoaderloader,Class[]interfaces,InvocationHandler h):返回代理類的一個實例,返回后的代理類可以當(dāng)作被代理類使用(可使用被代理類的在Subject接口中聲明過的方法)。
動態(tài)代理類及其實例通過 Proxy中的靜態(tài)方法newProxyInstance來創(chuàng)建,相當(dāng)于在內(nèi)存中動態(tài)產(chǎn)生了一個代理類 $Proxy0,然后再實例化,兩步驟合二為一。$Proxy0類只有一個構(gòu)造方法$Proxy0(java.lang.reflect. InvocationHandle)。因為是在內(nèi)存中動態(tài)產(chǎn)生的類,所以需要明確指定類加載器。這個代理類要實現(xiàn)一定的接口,動態(tài)代理實例中的對接口的調(diào)用都轉(zhuǎn)發(fā)給代理的調(diào)用處理器InvocationHandle的invoke()方法來完成真正的方法調(diào)用,該方法在調(diào)用目標(biāo)方法之前后或異常處理中增加額外功能。即:每執(zhí)行一次目標(biāo)類的方法便執(zhí)行一次invoke方法。例如,調(diào)用ArrayList類的add方法,那么JVM生成的動態(tài)代理類內(nèi)部實現(xiàn)為:
Java動態(tài)代理的通用類圖如圖2所示:
使用梁單元建立Midas Civil分析模型,填料采用彈性連接與節(jié)點荷載模擬,取填料重度22 kN/m3,主拱圈、立柱、腹拱、橋面系均采用C40素混凝土模擬,護欄作為二期恒載加載,腹拱與立柱、主拱圈為主從節(jié)點剛性連接,固定約束主拱圈拱腳。拱橋分析雙單元模型如圖4所示。
圖2 動態(tài)代理類圖
攔截器體系是struts2的一個重要組成部分,正是大量的內(nèi)建攔截器完成了該框架的大部分操作。攔截器也就是個普通的java類,之所以稱其為攔截器,只是就他的行為而言的。攔截器可以動態(tài)地攔截發(fā)送到指定Action的請求,通過攔截器機制可以在Action的前后插入某些代碼。通過這種方式,就可以把多個action中需要重復(fù)指定的代碼提取出來,放在攔截器里定義,從而更好的提供代碼重用性。
Struts框架之所以能夠調(diào)用攔截器的方法,調(diào)用哪一個攔截器,是因為攔截器的方法都是通過代理的方式調(diào)用的。代理類實現(xiàn)在調(diào)用目標(biāo)方法的前后分別調(diào)用攔截器中的方法,主要借助于Proxy類和InvocationHandler接口來實現(xiàn),JDK動態(tài)代理的關(guān)鍵在于下面的MyInvokation-Handler類,該類是一個InvocationHandler實現(xiàn)類,該實現(xiàn)類的invoke方法將會作為代理對象的方法實現(xiàn)。
invoke方法通過反射機制回調(diào)了被代理對象的目標(biāo)方法,在調(diào)用目標(biāo)方法前后分別附加了功能(也可以在異常處理中附加功能)。通過這種方式,使得代理對象的方法既回調(diào)了被代理對象的方法,并為被代理對象的方法增加其他功能。通過上面的代理處理器類可以看出類與攔截對象沒有絲毫耦合,從而提供了更好的解耦以及更好的擴展。但上面和兩個地方耦合了:(1)與攔截器類耦合,上面類固定使用了XXIntercepter攔截器;(2)與被攔截方法耦合。正是通過這兩個地方的耦合,系統(tǒng)才能夠知道在調(diào)用哪個攔截器的方法,以及需要攔截的目標(biāo)對象,解決這二個耦合的方案是將攔截器類和需要被攔截的目標(biāo)方法都放到配置文件中。
代理工廠負責(zé)根據(jù)目標(biāo)對象和對應(yīng)的攔截器生成新的代理對象,代理對象里的方法是目標(biāo)方法和攔截器方法的組合。所以采用動態(tài)代理可以非常靈活地實現(xiàn)解耦[3]。
這種動態(tài)代理在AOP(Aspect Orient Program,面向切面編程)里被稱為AOP代理。AOP的核心思想就是將應(yīng)用程序中的商業(yè)邏輯同對其提供支持的通用服務(wù)進行分離。AOP從關(guān)注點的角度考慮問題,關(guān)注點也就是要考察或解決的問題。AOP把一個系統(tǒng)分解成不同的關(guān)注點,核心的業(yè)務(wù)邏輯稱為核心關(guān)注點,而一些分散在各個部分輔助的關(guān)注點稱為橫切關(guān)注點。核心關(guān)注點通過面向?qū)ο蟮姆绞絹韺崿F(xiàn),橫切關(guān)注點通過AOP的方式來實現(xiàn),比如記錄日志、安全驗證等橫切關(guān)注點就可以通過AOP的方式來實現(xiàn)[4]。Spring的AOP實現(xiàn)就是基于動態(tài)代理實現(xiàn)的。
在一般軟件開發(fā)中,通常在一些業(yè)務(wù)邏輯中有些共用功能模塊,如:安全,事物,日志等,這些功能貫穿到好幾個模塊中,我們稱其為交叉業(yè)務(wù)。AOP的目標(biāo)就是使得交叉業(yè)務(wù)模塊化,使這些模塊移到原始方法的周圍,作為一個切面,這與在方法中編寫切面代碼的運行效果是一樣的。AOP是從程序的運行角度來考慮程序的流程,在程序特定的切面通過系統(tǒng)自動插入特定代碼[5]。
Hibernate采用“延遲加載”管理關(guān)聯(lián)實體的模式,其實就在加載主實體時,并未真正去抓取關(guān)聯(lián)實體對應(yīng)數(shù)據(jù),而只是動態(tài)地生成一個對象作為關(guān)聯(lián)實體的代理。當(dāng)應(yīng)用程序真正需要使用關(guān)聯(lián)實體時,代理對象會負責(zé)從底層數(shù)據(jù)庫抓取記錄,并初始化真正的關(guān)聯(lián)實體。在Hibernate的延遲加載中,客戶端程序開始獲取的只是一個動態(tài)生成的代理對象,而真正的實體則委托給代理對象來管理。
Hibernate的延遲加載(lazy load)本質(zhì)上就是代理模式的應(yīng)用,通過以上介紹得知,經(jīng)常通過代理模式來降低系統(tǒng)的內(nèi)存開銷、提升應(yīng)用的運行性能。Hibernate充分利用了代理模式的這種優(yōu)勢,并結(jié)合了 Javassist或 CGLIB來動態(tài)地生成代理對象,這更加增加了代理模式的靈活性。
采用數(shù)據(jù)庫連接池技術(shù)的資源釋放與傳統(tǒng)JDBC方式不同,用戶使用完連接之后再放到連接池中而不是銷毀,如果用戶在取出數(shù)據(jù)后,調(diào)用了connection的close方法,則該連接就會被銷毀,這就和連接池技術(shù)相違背了。通常認(rèn)為,用繼承的方法可以解決,即通過繼承各種關(guān)系數(shù)據(jù)庫驅(qū)動的connection類,重寫其中close方法即可。但這種方法有其局限性,一是因為難以擴展,各數(shù)據(jù)庫廠商的connection類不一樣,二是有些數(shù)據(jù)庫驅(qū)動的connection方法不允許繼承。為了解決此問題,需要使用代理模式來解決,即定義一個連接類 Myconnection作為代理,實現(xiàn)java.sql.connection類,重寫close()方法,根據(jù)“組合優(yōu)先繼承”的思想,把真實的數(shù)據(jù)庫連接組合到Myconnection中類驅(qū)動數(shù)據(jù)庫。這樣在數(shù)據(jù)源MyDataSource中創(chuàng)建連接時,以構(gòu)造方法中傳入具體連接類,代碼如下:
如果采用動態(tài)代理實現(xiàn),需要定義一個調(diào)用處理器來實現(xiàn)上述Myconnection的功能:
這樣在數(shù)據(jù)源MyDataSource中創(chuàng)建連接時,用調(diào)用處理器來綁定真實連接,代碼如下:
代理設(shè)計模式在軟件開發(fā)中是使用較多的一種設(shè)計模式,java提供的動態(tài)代理應(yīng)用比較廣泛,大到一個系統(tǒng)框架、企業(yè)平臺,小到代碼片段、事物處理,而且有了AOP的編程方式,代理使用就更加簡單了。通過以上分析可知,代理設(shè)計模式優(yōu)點主要有:
(1)職責(zé)清晰。真實的角色就是實現(xiàn)實際的業(yè)務(wù)邏輯,不用關(guān)心其他非本職責(zé)的事務(wù),其它事物可以通過后期的代理來完成,附帶的結(jié)果就是編程簡潔清晰。
(2)高擴展性。具體主題角色是隨時都會發(fā)生變化的,只要它實現(xiàn)了接口,不管它如何變化,代理類完全就可以在不做任何修改的情況下使用。
(3)智能化。代理的智能化體現(xiàn)在動態(tài)代理模式。
[1](美)弗里曼(Freeman,E.),等.Head First設(shè)計模式(中文版)[M].北京:中國電力出版社,2007.
[2]JDK api文檔[EB/OL].http://docs.oracle.com/ javase/6/docs/api/.
[3]李剛.Struts2.1權(quán)威指南——基于webwork核心的MVC開發(fā)[M].北京:電子工業(yè)出版社,2009.
[4]曹曉利,郭順生.AOP技術(shù)及其在J2EE中的動態(tài)代理實現(xiàn)[J].計算機技術(shù)與發(fā)展,2008,18(11):120-122.
[5]王小民,楊志輝,張雄,等.結(jié)合AOP與反射機制動態(tài)改變軟件的行為[J].計算機科學(xué),2007,34(11):274 -278.
TP311
A
1008—3340(2012)03—0066—04
2012-03-26
崔行臣,山東聊城人,碩士,研究方向為軟件方法與技術(shù)。