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