標籤:
程式碼範例
Java原生API中,動態代理常用的API有兩個:InvocationHandler介面和Proxy類
首先上代碼StaffLoggerAspect.java
public class StaffLoggerAspect implements InvocationHandler { Object target; public Object getObject(Object object) { target = object; return Proxy.newProxyInstance(Staff.class.getClassLoader(), Staff.class.getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(proxy.getClass().getName()); return method.invoke(target, args); }}
Main類的main方法
public static void main(String[] args) { StaffLoggerAspect aspect = new StaffLoggerAspect(); Staff staff1 = new Staff(); staff1.setName(""); System.out.println(staff1); Object staff2 = aspect.getObject(staff1); System.out.println(staff2);}
輸出結果
Staff{name=‘‘, age=0}com.sun.proxy.$Proxy0Staff{name=‘‘, age=0}
注意事項
先來解釋getObject方法,很多網上的教程都有某種函數用來做類似的事情,這個函數用來返回一個與被代理對象實現了相同介面的代理對象
注意它返回的是代理對象,而不是原對象。代理對象是Proxy類的子類(API文檔),實現了被代理對象的所有介面,所以對這個函數結果進行強制轉換的話必須轉換成對應的介面類型而不是物件類型,對於沒有介面的對象怎麼辦呢?只能轉換成Object對象了,當然你能夠通過代理使用的方法也只有Object內建的equals, toString之類的了,一些Object方法不會觸發invoke方法,詳見後邊Proxy對象特徵最後一條。代理對象的類名是預留類名詳見Java API對於Proxy類的解釋
Java API Proxy 部分原文及解釋 Proxy類的特徵
A proxy class has the following properties:
- Proxy classes are public, final, and not abstract. (即不可繼承)
- The unqualified name of a proxy class is unspecified. The space of class names that begin with the string “$Proxy” should be, however, reserved for proxy classes. (代理對象類名是沒有明確定義的,但是以$Proxy開頭的類名要給代理對象保留著,所以標準情況下如果發現某個class的類名以$Proxy開頭,那它肯定是代理對象,具體可見輸出結果)
- A proxy class extends java.lang.reflect.Proxy. (代理對象全是這個類的子類,所以可以用instanceof來判斷是否是代理對象)
- A proxy class implements exactly the interfaces specified at its creation, in the same order.(代理對象和被代理對象所實現介面完全一致,連順序也一致,順序是做什麼用的呢?原文檔後邊有一區段標頭是Methods Duplicated in Multiple Proxy Interfaces,這個順序即是用來處理多介面具有相同函式宣告的情況的,這裡不詳述)
- If a proxy class implements a non-public interface, then it will be defined in the same package as that interface. Otherwise, the package of a proxy class is also unspecified. Note that package sealing will not prevent a proxy class from being successfully defined in a particular package at runtime, and neither will classes already defined by the same class loader and the same package with particular signers.(好長一串,意思是:如果公用介面,那麼包名不確定,即使是包的內部介面,其它包訪問不了,如果進行了代理,那麼包裡也會多出來這麼一個代理類。後面說的跟代理無關了,說的是使用反射動態建立對象的話,包密閉是防不住色狼類的,完全阻止不了)
- Since a proxy class implements all of the interfaces specified at its creation, invoking getInterfaces on its Class object will return an array containing the same list of interfaces (in the order specified at its creation), invoking getMethods on its Class object will return an array of Method objects that include all of the methods in those interfaces, and invoking getMethod will find methods in the proxy interfaces as would be expected.(因為之前說的一些原理,所以代理對象的getInterfaces返回被代理對象所有介面,順序一致,代理對象的getMethod返回那些介面的方法)
- The Proxy.isProxyClass method will return true if it is passed a proxy class– a class returned by Proxy.getProxyClass or the class of an object returned by Proxy.newProxyInstance– and false otherwise.(代理對象兩種擷取方式:Proxy.getProxyClass或者Proxy.newProxyInstance其它方式擷取的Proxy.isProxyClass會返回false)
- The java.security.ProtectionDomain of a proxy class is the same as that of system classes loaded by the bootstrap class loader, such as java.lang.Object, because the code for a proxy class is generated by trusted system code. This protection domain will typically be granted java.security.AllPermission.(代理對象許可權極高,具有java.security.AllPermission,和java.lang.Object及其它的啟始物件一致)
- Each proxy class has one public constructor that takes one argument, an implementation of the interface InvocationHandler, to set the invocation handler for a proxy instance. Rather than having to use the reflection API to access the public constructor, a proxy instance can be also be created by calling the Proxy.newProxyInstance method, which combines the actions of calling Proxy.getProxyClass with invoking the constructor with an invocation handler.(代理類具有一個public單參數建構函式,需要InvocationHandler對象作為輸入參數)
Proxy對象的特徵
and the following cast operation will succeed (rather than throwing a ClassCastException):
(Foo) proxy
(首先要注意斷句,看英文的話implemented by後邊很容易搞錯,應該是its proxy class / Foo, 整體上是這樣的: Given a proxy instance proxy and one of its interfaces, for example Foo, … Foo是介面,不是代理類的對象!!!根據之前proxy對象的說明,這一點本身沒什麼難理解的,它一加說明反而容易弄錯)
Each proxy instance has an associated invocation handler, the one that was passed to its constructor. The static Proxy.getInvocationHandler method will return the invocation handler associated with the proxy instance passed as its argument.(廢話,就是說想要拿到某個代理對象的InvocationHandler的話調用Proxy.getInvocationHandler方法)
An interface method invocation on a proxy instance will be encoded and dispatched to the invocation handler‘s invoke method as described in the documentation for that method. (代理對象上方法調用會發送到與之關聯的InvocationHandler對象的invoke方法,注意所有方法調用都會被轉寄,需要在invoke裡判斷是不是你要弄的那個方法)
An invocation of the hashCode, equals, or toString methods declared in java.lang.Object on a proxy instance will be encoded and dispatched to the invocation handler‘s invoke method in the same manner as interface method invocations are encoded and dispatched, as described above. The declaring class of the Method object passed to invoke will be java.lang.Object. Other public methods of a proxy instance inherited from java.lang.Object are not overridden by a proxy class, so invocations of those methods behave like they do for instances of java.lang.Object.(hashCode, equals, toString方法也會轉寄到proxy對象InvocationHandler的invoke方法,其它沒有轉寄!!!重要!!!要注意!!!)
使用說明
文檔重要部分解釋完了,這裡補充一些實際使用時候用的上的包括思考方式等。
最開始我看到那個getObject裡target設定的不是Proxy對象有點轉不過來,其實要想清楚,過程是這樣的:
- 你在外邊用代理獲得的是proxy對象,而不是原來類型的對象了,所以return的是代理對象,這樣在調用介面的方法時才能轉寄過來
- 這裡儲存一個target是很有必要的,為什麼呢,因為要儲存被代理對象的資訊。像代理對象建立的時候完全不需要被代理對象的實體,只需要其類的資訊,而沒有記錄對象本身,範例程式碼裡Staff的name的值有很多可能,我們對於不同staff對象做代理的話,希望被代理對象對應的getName不會出問題,那麼我們需要一個原來對象的target來調用對應的方法,所以要記錄一下,因此,通常情況下這個target在getObject被賦值,在invoke方法中被使用
- invoke方法第一個參數是代理對象,而且不要在invoke裡調用代理對象上可以被轉寄的方法,比如你對toString進行了處理,然後你在invoke方法裡還調用了proxy對象的toString方法(比如隱式的:System.out.println(proxy)這樣),這種是絕對禁止的!因為你在代理對象上調用轉寄方法的話會引起再次調用invoke函數,從而導致像遞迴一樣的結果,而大部分作業系統對遞迴層數是有限制的,會引發嚴重後果,所以如果沒必要不要在invoke裡調用代理對象的方法,但是你調用System.out.println(target)是安全的,因為會進行轉寄的只有代理對象的對應方法,被代理對象的直接方法調用是不經過轉寄的!!!
使用Java API動態代理的注意事項就簡單說到這裡,對於Method對象的使用和注意事項請自行尋找相關API或教程,CGLib動態代理實現原理有所不同,相似性肯定有,但是請注意和Java原生API動態代理的區別(Java原生通過反射直接產生getInterfaces()得到的那些介面的一個對象,並不管被代理對象extends的部分,所以構造非常快,但是執行的時候效能低,因為需要各種轉寄;CGLib通過反射直接產生被代理對象的子類,所以不可用於final類的代理,因為這種類不可被繼承,同時會把invoke的內容寫入被產生的代理對象裡,所以產生的時候會很慢,但是這種對象直接就是被代理對象相同類型的對象,畢竟是產生被代理類的子類,所以使用方便,被代理對象可以不需要介面,而且執行方法速度很快。因為原理區別比較大,CGLib肯定還有其它與Java API不同的特徵,筆者暫時沒有時間研究CGLib的特點,暫不詳述)。在選擇上,對於單例模式的對象,或者說相比於對象方法調用的次數,構造次數很少的對象建議用CGLib,對於需要大量構造(比如資料庫的一條記錄,網路服務的一個Request對象),相對而言每個對象方法調用次數不是很多的對象,建議使用Java原生API。
發現文中有不當之處,希望指正,尤其CGLib和Java API比較部分,是從原理出發的一些推斷,並不確定,建議大家去看CGLib的相關文檔。
筆者撰文不易,轉載請註明出處,拜謝
使用 Java AOP API 完成動態代理的一些注意事項