Java中的代理和代理模式

Java中的代理和代理模式

1. 代理模式

在這裏插入圖片描述

代理模式通過爲某個對象提供一個代理來控制對於該對象的訪問。代理類主要負責爲委託類(真實對象)預處理消息、過濾消息、傳遞消息給委託類。代理類本身不負責具體的實現,而是利用委託類來完成具體的業務實現,並將執行結果進行封裝處理。

代理類爲委託類進行消息預處理、消息的過濾等操作後,將消息傳遞給被代理類(委託類),之後還可以進行消息的後處理操作。代理類和委託類通常會存在關聯關係,代理類本身不實現服務,而是通過調用委託類中的方法來提供服務。

代理模式可以分爲如下幾類:

  • 遠程代理
  • 保護代理
  • 緩存代理
  • 虛擬和智能代理

代理模式中包含有幾項要素:

  • Subject(共同接口):客戶端使用的現有接口
  • RealSubject(真實對象):真實對象的類
  • ProxySubject(代理對象):代理類

下面我們通過例子來理解一下代理模式。公司都有一個公關部門來對接媒體,當外面的媒體想要採訪公司的董事的時候,並不是直接進行採訪,而是通過公關部門來進行對接。如果公司有成就,公關部門負責大吹特吹;如果媒體有問題想要找公司詢問,公關部門負責說一些推諉搪塞的話,例如:這件事我們已經上報給相關的部門了,請回去等消息(這件事我們已經忘了,你繼續傻等吧),或者說這件事不歸我們管,你要去找xxx(別來找我們,找我們也不會解決)……

上面的例子中的公司就是委託對象,公關部門就是代理對象,媒體就是客戶端。


2. 靜態代理

所謂靜態,指的是接口、代理類和委託類在程序編譯期就已經被確定下來。靜態代理中的委託類和代理類都需要實現定義的接口,而代理類中持有一個委託類對象的引用,而後在代理類的方法中調用該對象的方法。

假設現在有一個Person接口,接口中有一個方法say()

public interface Person {
    void say(String name);
}

下面定義Star類實現Person接口,並重寫say()

public class Star implements Person{
    public Star() {
    }

    @Override
    public void say(String name) {
        System.out.println("Hello " + name);
    }
}

明星通常需要經紀人來對接外部的事務,因此,下面創建Agent類,它同樣需要實現Person接口並重寫say()。Agent類中有一個Star的對象,表示他所負責的明星,而且類中的say()只負責傳遞消息,真正使用到消息的是Star中的say()

public class Agent implements Person{
    Star star;

    public Agent(Star star) {
        this.star = star;
    }

    @Override
    public void say(String name) {
        star.say(name);
    }
}

最後通過測試類來看一下如何使用:

public class Client{
    public static void main(String[] args) {
        String name = "Forlogen";
        Star star = new Star();
        Agent agent = new Agent(star);
        agent.say(name);
    }
}
Hello Forlogen

首先創建Star類對象star,然後創建Agent對象並將star傳入。最後使用agent的say()來傳遞輸入的字符串,Agent類中的star的say()來使用消息,輸出結果。

總結:

  • 優點:在不改變委託類對象的前提下,可以選擇性的對委託類對象的功能進行擴展,例子中沒有添加擴展的相關邏輯
  • 缺點:代理類由於和委託類都實現了接口,因此,代理類實現了委託類要實現的所有方法。一旦接口中增加方法,兩者都需要進行改變,這樣不僅違背了開閉原則,而且增加了維護成本

3. 動態代理

動態相對於靜態來說,它指的是代理類並不是在編譯期就被確定,而是在運行期才被確定下來。在運行期中,根據程序中的指示來動態的生成代理類。動態代理相較於靜態代理的優勢在於,它可以很方便的對代理類的函數進行統一管理,而不用修改每個代理類中的方法。

首先來看一下如何使用動態代理,然後再分析它的原理。我們依然使用靜態代理中使用的例子,Person接口和Star實現類並不改變,Agent中體現動態代理:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class Agent implements InvocationHandler {
    Object obj;

    public Agent(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before invoke...");
        System.out.println("RealClass is:" + this.obj.toString() + " and method is: " + method.getName());
        method.invoke(obj, args);
        System.out.println("after invoke...");
        return null;
    }


    public Object getProxyInstance(){
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }
}

在使用時,首先創建Star和Agent類對象,然後通過Agent的類對象調用getProxyInstance()來獲取代理類對象,最後使用對象來執行say()

public class Client{
    public static void main(String[] args) {
        System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        
        Star star = new Star();
        Agent agent = new Agent(star);
        Person proxyInstance = (Person)agent.getProxyInstance();
        proxyInstance.say("Forlogen");
    }
}
before invoke...
RealClass is:Proxy.DynamicProxy.Star@511d50c0 and method is: say
Hello Forlogen
after invoke...

現在我們再來具體看一下Agent類的實現。現在Agent並沒有選擇去實現Person接口,而是實現InvocationHandler接口並重寫其中的invoke()。通過代理對象來調用委託類中的方法時,最後都是委託給invoke()來執行,invoke()中可以對委託類進行一系列的前置增強後置增強操作。最後使用的是Proxy類中的靜態方法static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler );來獲取代理對象,通過代理對象來執行Person接口中定義的方法,也就是Star類中想要執行的方法。

package java.lang.reflect;
public class Proxy implements java.io.Serializable {
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // ...
    }
}

參數分析:

  • ClassLoader loader:指定一個動態加載代理類的類加載器
  • Class<?>[] interfaces::指明委託類實現的接口,之後通過拼接字節碼生成的類才能知道調用哪些方法。
  • InvocationHandler h:這是一個方法委託類,通過代理調用被代理類的方法時,就可以將方法名和方法參數都委託給這個委託類

動態代理使用的過程中,我們只能看到代理類,委託類和代理類之間通過InvocationHandler來實現代理的過程。

總結,動態代理的具體步驟如下:

  • 通過實現InvocationHandler接口創建自己的調用處理器(Handler)
  • 通過爲Proxy類指定類加載器對象和一組Interface來創建代理類
  • 通過反射機制獲得動態代理類的構造函數,其唯一參數類型是調用處理器的接口類型
  • 通過構造函數創建動態代理類實例,構造時調用處理器對象作爲參數傳入

4. 源碼分析

在這裏插入圖片描述

體現動態代理的類要實現InvocationHandler接口並重寫invoke(),而代理類對象的獲取首先要通過Proxy.newProxyInstance()實現,下面看一下它的源碼實現:

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        // 檢查傳入的Handler是否爲空
        Objects.requireNonNull(h);
		
        // 拷貝代理類代理的接口
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        // 生成代理類對象
        Class<?> cl = getProxyClass0(loader, intfs);

        //使用指定的Handler來獲取代理類的構造函數
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }
			// 通過反射機制獲得指定代理類的構造函數
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            // 檢查訪問權限是否爲public
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    // 設置訪問權限
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            // 調用newInstance()創建Proxy代理實例
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

newProxyInstance()的源碼實現可以看出,它所執行的主要操作有:

  • 調用getProxyClass0()來獲取代理類對象
  • 調用getConstructor()通過反射獲取代理類對象的構造函數
  • 設置訪問權限爲public
  • 通過反射中的newInstance()來創建Proxy代理實例

上面使用到的三個主要的方法,getConstructornewInstance()是java.lang.reflect包下的函數,熟悉反射機制的應該清楚。我們重點看下getProxyClass0()的源碼實現:

	private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 接口數量不應大於65535,否則拋異常
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // 如果指定接口的代理類在緩存中已存在,則直接讀取
        // 否則通過ProxyClassFactory工廠來獲取代理對象
        return proxyClassCache.get(loader, interfaces);
    }

proxyClassCache的實現仍然是通過代理模式:

	/**
     * a cache of proxy classes
     */
    private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

代理對象需要使用工廠ProxyClassFactory獲取,它的的源碼實現如下:

    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // 代理類的名字的前綴統一爲“$Proxy”
        private static final String proxyClassNamePrefix = "$Proxy";

        // 每個代理類前綴後面都會跟着一個唯一的編號,如$Proxy0、$Proxy1、$Proxy2
        // 例如例子中只代理了一個接口,應該字節碼文件反編譯後只有$Proxy0
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                // 驗證類加載器加載接口得到對象是否與由apply函數參數傳入的對象相同
                Class<?> interfaceClass = null;
                try {
                    // 獲取接口的Class類對象
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                //驗證interfaceClass是不是接口,因爲動態代理只能代理接口
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
				
                // 將interfaceClass存到Map集合中
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            // 記錄不是被public修飾的代理接口的包名,用來幫助在相同的包下定義代理類
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            //  選擇一個要生成的代理類的名字
            long num = nextUniqueNumber.getAndIncrement();
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

           // 生成代理類
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

上面主要執行的是通過工廠來獲取指定類加載器和接口的代理類,其中主要使用的是generateProxyClass()來實現代理類的獲取,它的源碼實現爲:

/*
    var0:代理類的名字
    var1:要代理的具體接口
*/
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    	// 生成代理類的字節碼文件
        final byte[] var4 = var3.generateClassFile();
    	// 保存代理類的字節碼文件
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }

                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }

        return var4;
    }

generateProxyClass()方法的返回類型是一個字節數組,表示代理類的字節碼文件信息。而字節碼文件的生成使用了generateClassFile(),它的源碼實現爲:

    private byte[] generateClassFile() {
        //下面一系列的addProxyMethod方法是將接口中的方法和Object中的方法添加到代理方法中(proxyMethod)
        this.addProxyMethod(hashCodeMethod, Object.class);
        this.addProxyMethod(equalsMethod, Object.class);
        this.addProxyMethod(toStringMethod, Object.class);
        Class[] var1 = this.interfaces;
        int var2 = var1.length;

        int var3;
        Class var4;
        //獲得接口中所有方法並添加到代理方法中
        for(var3 = 0; var3 < var2; ++var3) {
            var4 = var1[var3];
            Method[] var5 = var4.getMethods();
            int var6 = var5.length;

            for(int var7 = 0; var7 < var6; ++var7) {
                Method var8 = var5[var7];
                this.addProxyMethod(var8, var4);
            }
        }

        Iterator var11 = this.proxyMethods.values().iterator();

        List var12;
        while(var11.hasNext()) {
            var12 = (List)var11.next();
            checkReturnTypes(var12);
        }

        Iterator var15;
        try {
            //生成代理類的構造函數
            this.methods.add(this.generateConstructor());
            var11 = this.proxyMethods.values().iterator();

            while(var11.hasNext()) {
                var12 = (List)var11.next();
                var15 = var12.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.ProxyMethod var16 = (ProxyGenerator.ProxyMethod)var15.next();
                    this.fields.add(new ProxyGenerator.FieldInfo(var16.methodFieldName, "Ljava/lang/reflect/Method;", 10));
                    this.methods.add(var16.generateMethod());
                }
            }

            this.methods.add(this.generateStaticInitializer());
        } catch (IOException var10) {
            throw new InternalError("unexpected I/O Exception", var10);
        }

        if (this.methods.size() > 65535) {
            throw new IllegalArgumentException("method limit exceeded");
        } else if (this.fields.size() > 65535) {
            throw new IllegalArgumentException("field limit exceeded");
        } else {
            this.cp.getClass(dotToSlash(this.className));
            this.cp.getClass("java/lang/reflect/Proxy");
            var1 = this.interfaces;
            var2 = var1.length;

            for(var3 = 0; var3 < var2; ++var3) {
                var4 = var1[var3];
                this.cp.getClass(dotToSlash(var4.getName()));
            }

            this.cp.setReadOnly();
            ByteArrayOutputStream var13 = new ByteArrayOutputStream();
            DataOutputStream var14 = new DataOutputStream(var13);

            try {
                var14.writeInt(-889275714);
                var14.writeShort(0);
                var14.writeShort(49);
                this.cp.write(var14);
                var14.writeShort(this.accessFlags);
                var14.writeShort(this.cp.getClass(dotToSlash(this.className)));
                var14.writeShort(this.cp.getClass("java/lang/reflect/Proxy"));
                var14.writeShort(this.interfaces.length);
                Class[] var17 = this.interfaces;
                int var18 = var17.length;

                for(int var19 = 0; var19 < var18; ++var19) {
                    Class var22 = var17[var19];
                    var14.writeShort(this.cp.getClass(dotToSlash(var22.getName())));
                }

                var14.writeShort(this.fields.size());
                var15 = this.fields.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.FieldInfo var20 = (ProxyGenerator.FieldInfo)var15.next();
                    var20.write(var14);
                }

                var14.writeShort(this.methods.size());
                var15 = this.methods.iterator();

                while(var15.hasNext()) {
                    ProxyGenerator.MethodInfo var21 = (ProxyGenerator.MethodInfo)var15.next();
                    var21.write(var14);
                }

                var14.writeShort(0);
                return var13.toByteArray();
            } catch (IOException var9) {
                throw new InternalError("unexpected I/O Exception", var9);
            }
        }
    }

字節碼生成後,它是作爲工廠方法中調用defineClass0()的參數,最終是使用defineClass0()來解析字節碼,生成了Proxy的Class對象。defineClass0()是一個本地方法,這裏就無法看它的具體實現了。

private static native Class<?> defineClass0(ClassLoader loader, String name,
                                                byte[] b, int off, int len);

至於JVM如何通過類加載子系統和運行時數據區的操作來從字節碼文件中創建對象,可以查看JVM原理的相關內容。有關對象創建的內容可閱讀對象的實例化、內存佈局和訪問定位瞭解。更多JVM的內容可查閱:

JVM內存模型

類加載子系統

運行時數據區

方法區

最後看一下Person接口的代理類對應的字節碼文件反編譯後的結果。首選,在動態代理的使用程序中添加

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

來獲取代理類的.class文件,文件在自己工程的com.sun.proxy包下。最後反編譯後得到:

package com.sun.proxy;

import Proxy.DynamicProxy.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Person {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void say(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Proxy.DynamicProxy.Person").getMethod("say", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

$Proxy0繼承了Proxy並實現了要代理的接口Person,這也就說明了動態代理只能代理接口而不能代理類,因爲Java中的繼承只能是單繼承。$Proxy0中的方法包含了Object類中的方法和接口中要實現的方法,靜態代碼塊中通過Class.forName()來獲取指定全限定類名對應類的的Class類對象,然後使用getMethod()來獲取指定名字的方法,這些工作都是通過反射機制實現,例如Person接口中的say()對應得到的是m3。下面把$Proxy0中say()的實現單獨抽出來看一下:

public final void say(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

發現方法的實現仍然使用的是反射中的invoke(),方法的參數爲代理類、方法名和傳入的String類型的參數。

通過對動態代理實現過程的源碼分析,我們現在可以明白爲什麼通過Proxy.newProxyInstance()可以按指定的參數來獲取接口的代理類,爲什麼參數中要傳入InvocationHandler接口的實現類,爲什麼說最後方法都委託給實現類的invoke(),這一切的工作都依賴於反射機制和Java虛擬機。


5. Cglib代理

靜態代理和動態代理都要求目標對象實現某個接口,但有時目標對象只是一個單獨的對象,並沒有實現任何接口,如果使用目標對象自雷來實現代理的方式就成爲Cglib代理,也稱爲爲子類代理。它是在內存中構建一個子類對象從而實現目標對象功能擴展。

Cglib是一個強大的高性能的代碼生成包,它可以在運行期擴展Java類與實現Java接口,使用它需要現在相關的Jar包,然後將其引入到項目的路徑中。

通常來說,如果目標對象需要實現接口,那麼使用上面的動態代理;如果目標對象不需要實現接口,那麼使用Cglib代理。

依然使用前面的例子來看一下Cglib代理如何使用,現在Person接口不再使用,Star不需要實現任何接口:

public class Star {
    public void say(String name) {
        System.out.println("Hello " + name);
    }
}

Agent的實現:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class Agent implements MethodInterceptor {
    private Object obj;

    public Agent(Object obj) {
        this.obj = obj;
    }

    public Object getProxyInstance(){
        // 創建工具類
        Enhancer enhancer = new Enhancer();
        // 設置父類,即委託類
        enhancer.setSuperclass(obj.getClass());
        // 設置回調函數
        enhancer.setCallback(this);
        //創建子類對象,即代理對象
        return enhancer.create();
    }
	
    // 重寫intercept方法,調用目標對象的方法
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before cglib proxy...");
        method.invoke(obj, args);
        System.out.println("after cglib proxy...");
        return null;
    }
}

這裏只做了簡單的介紹,詳細的原理等深入學習了再做整理。


6. 參考

Java代理(Proxy)模式

JAVA動態代理

java動態代理實現與原理詳細分析

Java 動態代理作用是什麼?

詳解java動態代理機制以及使用場景(一)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章