日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

靜態(tài)代理和動態(tài)代理的理解

 漢無為 2018-08-26

Java 靜態(tài)代理

靜態(tài)代理通常用于對原有業(yè)務(wù)邏輯的擴(kuò)充。比如持有二方包的某個類,并調(diào)用了其中的某些方法。然后出于某種原因,比如記錄日志、打印方法執(zhí)行時間,但是又不好將這些邏輯寫入二方包的方法里。所以可以創(chuàng)建一個代理類實現(xiàn)和二方方法相同的方法,通過讓代理類持有真實對象,然后在原代碼中調(diào)用代理類方法,來達(dá)到添加我們需要業(yè)務(wù)邏輯的目的。

這其實也就是代理模式的一種實現(xiàn),通過對真實對象的封裝,來實現(xiàn)擴(kuò)展性。

一個典型的代理模式通常有三個角色,這里稱之為**代理三要素**

共同接口


  1. public interface Action {
  2. public void doSomething();
  3. }

真實對象


  1. public class RealObject implements Action{
  2. public void doSomething() {
  3. System.out.println('do something');
  4. }
  5. }

代理對象


  1. public class Proxy implements Action {
  2. private Action realObject;
  3. public Proxy(Action realObject) {
  4. this.realObject = realObject;
  5. }
  6. public void doSomething() {
  7. System.out.println('proxy do');
  8. realObject.doSomething();
  9. }
  10. }

運(yùn)行代碼


  1. Proxy proxy = new Proxy(new RealObject());
  2. proxy.doSomething();

靜態(tài)代理和動態(tài)代理的理解

這種代理模式也最為簡單,就是通過proxy持有realObject的引用,并進(jìn)行一層封裝。

靜態(tài)代理的優(yōu)點(diǎn)和缺點(diǎn)

先看看代理模式的優(yōu)點(diǎn): 擴(kuò)展原功能,不侵入原代碼。

再看看這種代理模式的缺點(diǎn):

假如有這樣一個需求,有十個不同的RealObject,同時我們要去代理的方法是不同的,比要代理方法:doSomething、doAnotherThing、doTwoAnotherThing,添加代理前,原代碼可能是這樣的:


  1. realObject.doSomething();
  2. realObject1.doAnotherThing();
  3. realObject2.doTwoAnother();

為了解決這個問題,我們有方案一:

為這些方法創(chuàng)建不同的代理類,代理后的代碼是這樣的:


  1. proxy.doSomething();
  2. proxy1.doAnotherThing();
  3. proxy2.doTwoAnother();

當(dāng)然,也有方案二:

通過創(chuàng)建一個proxy,持有不同的realObject,實現(xiàn)Action1、Action2、Action3接口,來讓代碼變成這樣:


  1. proxy.doSomething();
  2. proxy.doAnotherThing();
  3. proxy.doTwoAnother();

于是你的代理模型會變成這樣:

靜態(tài)代理和動態(tài)代理的理解

毫無疑問,僅僅為了擴(kuò)展同樣的功能,在方案一種,我們會重復(fù)創(chuàng)建多個邏輯相同,僅僅RealObject引用不同的Proxy。

而在方案二中,會導(dǎo)致proxy的膨脹,而且這種膨脹往往是無意義的。此外,假如方法簽名是相同的,更需要在調(diào)用的時候引入額外的判斷邏輯。

java 動態(tài)代理

搞清楚靜態(tài)代理的缺點(diǎn)十分重要,因為動態(tài)代理的目的就是為了解決靜態(tài)代理的缺點(diǎn)。通過使用動態(tài)代理,我們可以通過在運(yùn)行時,動態(tài)生成一個持有RealObject、并實現(xiàn)代理接口的Proxy,同時注入我們相同的擴(kuò)展邏輯。哪怕你要代理的RealObject是不同的對象,甚至代理不同的方法,都可以動過動態(tài)代理,來擴(kuò)展功能。

簡單理解,動態(tài)代理就是我們上面提到的方案一,只不過這些proxy的創(chuàng)建都是自動的并且是在運(yùn)行期生成的。

動態(tài)代理基本用法

使用動態(tài)代理,需要將要擴(kuò)展的功能寫在一個InvocationHandler 實現(xiàn)類里:


  1. public class DynamicProxyHandler implements InvocationHandler {
  2. private Object realObject;
  3. public DynamicProxyHandler(Object realObject) {
  4. this.realObject = realObject;
  5. }
  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7. //代理擴(kuò)展邏輯
  8. System.out.println('proxy do');
  9. return method.invoke(realObject, args);
  10. }
  11. }

這個Handler中的invoke方法中實現(xiàn)了代理類要擴(kuò)展的公共功能。

到這里,需要先看一下這個handler的用法:


  1. public static void main(String[] args) {
  2. RealObject realObject = new RealObject();
  3. Action proxy = (Action) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Action.class}, new DynamicProxyHandler(realObject));
  4. proxy.doSomething();
  5. }

Proxy.newProxyInstance 傳入的是一個ClassLoader, 一個代理接口,和我們定義的handler,返回的是一個Proxy的實例。

仔細(xì)體會這個過程,其實有點(diǎn)類似我們在靜態(tài)代理中提到的方案一,生成了一個包含我們擴(kuò)展功能,持有RealObject引用,實現(xiàn)Action接口的代理實例Proxy。只不過這個Proxy不是我們自己寫的,而是java幫我們生成的,有沒有一點(diǎn)動態(tài)的味道。

讓我們再回顧一下代理三要素:真實對象:RealObject,代理接口:Action,代理實例:Proxy

上面的代碼實含義也就是,輸入 RealObject、Action,返回一個Proxy。妥妥的代理模式。

綜上,動態(tài)生成+代理模式,也就是動態(tài)代理。

網(wǎng)上搜了不少文章,到了這里,接下來就是和cglib等動態(tài)代理實現(xiàn)方法做一下橫向比較。本文不做橫向比較,為了不偏離主題,接下來做縱向挖掘。

看一下源碼

道理清楚了,但是這篇文章題目是搞懂,所以來看一下這個Proxy是如何自動被生成的。入口就在newProxyInstance方法,核心代碼如下:


  1. private static final Class[] constructorParams =
  2. { InvocationHandler.class };
  3. public static Object newProxyInstance(ClassLoader loader,
  4. Class[] interfaces,
  5. InvocationHandler h)
  6. throws IllegalArgumentException
  7. {
  8. Class cl = getProxyClass0(loader, intfs);
  9. ...
  10. final Constructor cons = cl.getConstructor(constructorParams);
  11. if (!Modifier.isPublic(cl.getModifiers())) {
  12. AccessController.doPrivileged(new PrivilegedAction() {
  13. public Void run() {
  14. cons.setAccessible(true);
  15. return null;
  16. }
  17. });
  18. }
  19. return cons.newInstance(new Object[]{h});
  20. }

整體流程就是:

1、生成代理類Proxy的Class對象。

2、如果Class作用域為私有,通過 setAccessible 支持訪問

3、獲取Proxy Class構(gòu)造函數(shù),創(chuàng)建Proxy代理實例。

生成Proxy的Class文件

生成Class對象的方法中,先是通過傳進(jìn)來的ClassLoader參數(shù)和Class[] 數(shù)組作為組成鍵,維護(hù)了一個對于Proxy的Class對象的緩存。這樣需要相同Proxy的Class對象時,只需要創(chuàng)建一次。

第一次創(chuàng)建該Class文件時,為了線程安全,方法進(jìn)行了大量的處理,最后會來到ProxyClassFactory的apply方法中,經(jīng)過以下流程:

1、校驗傳入的接口是否由傳入的ClassLoader加載的。

2、校驗傳入是否是接口的Class對象。

3、校驗是否傳入重復(fù)的接口。

4、拼裝代理類包名和類名,生成.class 文件的字節(jié)碼。

5、調(diào)用native方法,傳入字節(jié)碼,生成Class對象。


  1. proxyPkg = ReflectUtil.PROXY_PACKAGE + '.';
  2. long num = nextUniqueNumber.getAndIncrement();
  3. String proxyName = proxyPkg + proxyClassNamePrefix + num;
  4. byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
  5. proxyName, interfaces, accessFlags);
  6. return defineClass0(loader, proxyName,
  7. proxyClassFile, 0, proxyClassFile.length);

看一下第四步生成.class文件字節(jié)碼的過程,主要分為兩個階段:


  1. addProxyMethod(hashCodeMethod, Object.class);
  2. addProxyMethod(equalsMethod, Object.class);
  3. addProxyMethod(toStringMethod, Object.class);
  4. for (int i = 0; i < interfaces.length;="" i++)="">
  5. Method[] methods = interfaces[i].getMethods();
  6. for (int j = 0; j < methods.length;="" j++)="">
  7. addProxyMethod(methods[j], interfaces[i]);
  8. }
  9. }
  10. methods.add(this.generateConstructor());
  11. for (List sigmethods : proxyMethods.values()) {
  12. for (ProxyMethod pm : sigmethods) {
  13. fields.add(new FieldInfo(pm.methodFieldName,
  14. 'Ljava/lang/reflect/Method;', ACC_PRIVATE | ACC_STATIC));
  15. methods.add(pm.generateMethod());
  16. }
  17. }
  18. methods.add(generateStaticInitializer());

第一個階段的代碼比較清晰,主要就是添加各種Method,比如toString()、equals,以及傳入的代理接口中的方法。再添加一下構(gòu)造方法以及靜態(tài)初始化方法。這要構(gòu)成了一個對象,存儲生成Proxy的Class的一些信息。

到了這里,已經(jīng)把要構(gòu)造的Proxy的方法基本定義完成了,接下來就要生成這個.class文件了。


  1. ByteArrayOutputStream bout = new ByteArrayOutputStream();
  2. DataOutputStream dout = new DataOutputStream(bout);
  3. dout.writeInt(0xCAFEBABE);
  4. ...
  5. dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
  6. ...
  7. return bout.toByteArray();

看到這個CAFEBABE,就清楚第二階段的內(nèi)容了。CAFEBABE是Class文件的魔數(shù),關(guān)于Class文件這個咖啡寶貝的魔數(shù),相信做Java的人都知道。沒錯,第二階段就是生成字節(jié)碼。按JVM規(guī)范,寫入Class文件中包括權(quán)限控制、方法表、字段表等內(nèi)容,生成符合規(guī)范的Class文件。最后返回對應(yīng)的字節(jié)碼。

字節(jié)碼生成以后,通過調(diào)用native方法defineClass解析字節(jié)碼,就生成了Proxy的Class對象。

Proxy構(gòu)造方法

看一下Proxy的構(gòu)造方法字節(jié)碼生成部分:


  1. MethodInfo minfo = new MethodInfo('', '(Ljava/lang/reflect/InvocationHandler;)V',ACC_PUBLIC);
  2. DataOutputStream out = new DataOutputStream(minfo.code);
  3. code_aload(0, out);
  4. code_aload(1, out);
  5. out.writeByte(opc_invokespecial);
  6. out.writeShort(cp.getMethodRef(superclassName,'', '(Ljava/lang/reflect/InvocationHandler;)V'));
  7. ...

關(guān)鍵在于,生成了一個參數(shù)為InvocationHandler的構(gòu)造方法,code加載的是jvm方法區(qū)中的代碼,然后通過invokespecial指令調(diào)用了父類構(gòu)造方法。

查看生成的Class文件

上面利用字節(jié)碼生成技術(shù)產(chǎn)生Class文件的過程,看起來可能比較晦澀,其實我們可以查看這個產(chǎn)生的Proxy到底是個什么樣子。

注意ProxyGenerator中有這樣一個邏輯:


  1. if(saveGeneratedFiles) {
  2. ...
  3. FileOutputStream file = new FileOutputStream(dotToSlash(name) + '.class');
  4. file.write(classFile);
  5. ...
  6. }

再看一下saveGeneratedFiles這個變量:


  1. private final static boolean saveGeneratedFiles =
  2. java.security.AccessController.doPrivileged(
  3. new GetBooleanAction('sun.misc.ProxyGenerator.saveGeneratedFiles'))
  4. .booleanValue();

這是一個final類型的變量,通過GetBooleanAction方法讀取系統(tǒng)變量,獲取系統(tǒng)設(shè)置。默認(rèn)這個值是false,稍微看一下System這個類的源碼,發(fā)現(xiàn)有可以設(shè)置系統(tǒng)變量的Api,然后在程序的main 函數(shù)設(shè)置一下這個變量:

System.getProperties().setProperty('sun.misc.ProxyGenerator.saveGeneratedFiles', 'true');

這個時候,再跑一遍程序,就可以看到生成的Proxy的Class文件了,直接雙擊利用 ide 反編譯。


  1. package com.sun.proxy;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.Method;
  4. import java.lang.reflect.Proxy;
  5. import java.lang.reflect.UndeclaredThrowableException;
  6. public final class $Proxy0 extends Proxy implements Action {
  7. private static Method m1;
  8. private static Method m3;
  9. private static Method m2;
  10. private static Method m0;
  11. public $Proxy0(InvocationHandler var1) throws {
  12. super(var1);
  13. }
  14. public final void doSomething() throws {
  15. try {
  16. super.h.invoke(this, m3, (Object[])null);
  17. } catch (RuntimeException | Error var2) {
  18. throw var2;
  19. } catch (Throwable var3) {
  20. throw new UndeclaredThrowableException(var3);
  21. }
  22. }
  23. ...
  24. static {
  25. try {
  26. ...
  27. m3 = Class.forName('Action').getMethod('doSomething', new Class[0]);
  28. } catch (NoSuchMethodException var2) {
  29. throw new NoSuchMethodError(var2.getMessage());
  30. } catch (ClassNotFoundException var3) {
  31. throw new NoClassDefFoundError(var3.getMessage());
  32. }
  33. }
  34. }

省略一些無關(guān)代碼,可以看到兩個重要的方法。

一個就是我們的代理方法doSomething、另一個就是構(gòu)造方法。

這個$Proxy0 繼承 Proxy并調(diào)用了父類的構(gòu)造方法,回憶一下上文提到的invokeSpecial,怎么樣,對上了吧。

看一下Proxy中這個構(gòu)造方法:


  1. protected Proxy(InvocationHandler h) {
  2. Objects.requireNonNull(h);
  3. this.h = h;
  4. }

在看一下$Proxy0 的代理方法:

super.h.invoke(this, m3, (Object[])null);

再來回顧一下生成Proxy實例的過程:


  1. private static final Class[] constructorParams =
  2. { InvocationHandler.class };
  3. ...
  4. final Constructor cons = cl.getConstructor(constructorParams);
  5. ...
  6. return cons.newInstance(new Object[]{h});

其實newInstance生成Proxy實例時,通過$Proxy0的Class對象,選擇了這個InvocationHandler為參數(shù)的構(gòu)造方法,傳入我們定義的InvocationHandler并生成了一個 Proxy0的實例!InvocationHandler 里有realObject的邏輯以及我們的擴(kuò)展邏輯,當(dāng)我們調(diào)用Proxy0的doSomething方法時,就會調(diào)用到我們InvocationHandler 里 實現(xiàn)的invoke方法。

對上面這個過程,做一張圖總結(jié)一下:

靜態(tài)代理和動態(tài)代理的理解

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多