溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

JDK動態(tài)代理的實現(xiàn)方法

發(fā)布時間:2021-07-12 16:48:36 來源:億速云 閱讀:123 作者:chen 欄目:編程語言

這篇文章主要講解了“JDK動態(tài)代理的實現(xiàn)方法”,文中的講解內(nèi)容簡單清晰,易于學(xué)習(xí)與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學(xué)習(xí)“JDK動態(tài)代理的實現(xiàn)方法”吧!

JDK動態(tài)代理到底是怎么實現(xiàn)?

JDK的動態(tài)代理的類看不見摸不著,雖然可以看到效果,但是底層到底是怎么做的,為什么要求實現(xiàn)接口呢?

從Proxy.newProxyInstance入手

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
        // 判空,判斷 h 對象是否為空,為空就拋出 NullPointerException
        Objects.requireNonNull(h);
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
         // 進(jìn)行包訪問權(quán)限、類加載器等權(quán)限檢查
           checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /*
         * Look up or generate the designated proxy class.
         * 查找或生成指定的代理類
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        // 省略若干代碼
    }

第一步,嘗試獲取代理類,該代理類可能會被緩存,如果沒有緩存,那么進(jìn)行生成邏輯.

java.lang.reflect.Proxy#getProxyClass0

    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 數(shù)量超過 65535 就拋出異常,665535 這個就不用說了吧
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // 如果代理類已經(jīng)通過類加載器對給定的接口進(jìn)行實現(xiàn)了,那么從緩存中返回其副本
        // 否則,它將通過ProxyClassFactory創(chuàng)建代理類
        return proxyClassCache.get(loader, interfaces);
    }

最后發(fā)現(xiàn)會對生成的代理類進(jìn)行緩存,有了,就不直接返回,沒有的,還得生成代理類,我們繼續(xù)往下走:

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

java.lang.reflect.Proxy.ProxyClassFactory#apply

關(guān)鍵點在于 ProxyClassFactory 這個類,從名字也可以猜出來這個類的作用。看看代碼:

/**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names 定義前綴
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names  原子操作,適用于多線程
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
		            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
            // 通過反射獲取到接口類
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
         // 所得到的接口類與傳進(jìn)來的不相等,說明不是同一個類
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate. 
                 */
                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;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            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 + ".";
            }
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
       // 生產(chǎn)代理類的名字
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            // 一些驗證、緩存、同步的操作,不是我們研究的重點
            /*
             * Generate the specified proxy class.
             * 生成特殊的代理類
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);``

這段代碼即為生成動態(tài)代理類的關(guān)鍵,執(zhí)行完后會返回該描述該代理類的字節(jié)碼數(shù)組.隨后程序讀取該字節(jié)碼數(shù)組,將其轉(zhuǎn)化為運行時的數(shù)據(jù)結(jié)構(gòu)-Class對象,作為一個常規(guī)類使用.

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        // 如果聲明了需要持久化代理類,則進(jìn)行磁盤寫入.
        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;
    }

這里我們找到了一個關(guān)鍵的判斷條件-saveGeneratedFiles,即是否需要將代理類進(jìn)行持久化.

ProxyGenerator.generateProxyClass

public class ProxyGeneratorUtils {
    /**
     * 把代理類的字節(jié)碼寫到硬盤上 
     * @param path 保存路徑 
     */
    public static void writeProxyClassToHardDisk(String path) {
// 獲取代理類的字節(jié)碼  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.class.getInterfaces());
 
        FileOutputStream out = null;
 
        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

可以發(fā)現(xiàn),在根目錄下生成了一個 $Proxy0.class 文件,文件內(nèi)容反編譯后如下:

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

public final class $Proxy0 extends Proxy implements Person
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意這里是生成代理類的構(gòu)造方法,方法參數(shù)為InvocationHandler類型,看到這,是不是就有點明白
  *為何代理對象調(diào)用方法都是執(zhí)行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
  *被代理對象的實例,不禁會想難道是....? 沒錯,就是你想的那樣。
  *
  *super(paramInvocationHandler),是調(diào)用父類Proxy的構(gòu)造方法。
  *父類持有:protected InvocationHandler h;
  *Proxy構(gòu)造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *     }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //這個靜態(tài)塊本來是在最后的,我把它拿到前面來,方便描述
   static
  {
    try
    {
      //看看這兒靜態(tài)塊兒里面有什么,是不是找到了giveMoney方法。請記住giveMoney通過反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.Person").getMethod("giveMoney", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *這里調(diào)用代理對象的giveMoney方法,直接就調(diào)用了InvocationHandler中的invoke方法,并把m3傳了進(jìn)去。
  *this.h.invoke(this, m3, null);這里簡單,明了。
  *來,再想想,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象,
  *再聯(lián)系到InvacationHandler中的invoke方法。嗯,就是這樣。
  */
  public final void giveMoney()
    throws 
  {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  //注意,這里為了節(jié)省篇幅,省去了toString,hashCode、equals方法的內(nèi)容。原理和giveMoney方法一毛一樣。

}
  • jdk 為我們的生成了一個叫 $Proxy0(這個名字后面的0是編號,有多個代理類會一次遞增)的代理類,這個類文件時放在內(nèi)存中的,我們在創(chuàng)建代理對象時,就是通過反射獲得這個類的構(gòu)造方法,然后創(chuàng)建的代理實例。通過對這個生成的代理類源碼的查看,我們很容易能看出,動態(tài)代理實現(xiàn)的具體過程。

  • 我們可以對 InvocationHandler 看做一個中介類,中介類持有一個被代理對象,在 invoke 方法中調(diào)用了被代理對象的相應(yīng)方法,而生成的代理類中持有中介類,因此,當(dāng)我們在調(diào)用代理類的時候,就是再調(diào)用中介類的 invoke 方法,通過反射轉(zhuǎn)為對被代理對象的調(diào)用。

  • 代理類調(diào)用自己方法時,通過自身持有的中介類對象來調(diào)用中介類對象的 invoke 方法,從而達(dá)到代理執(zhí)行被代理對象的方法。也就是說,動態(tài)代理通過中介類實現(xiàn)了具體的代理功能。

  • 生成的代理類:$Proxy0 extends Proxy implements Person,我們看到代理類繼承了 Proxy 類,所以也就決定了 java 動態(tài)代理只能對接口進(jìn)行代理,Java 的繼承機(jī)制注定了這些動態(tài)代理類們無法實現(xiàn)對 class 的動態(tài)代理。

感謝各位的閱讀,以上就是“JDK動態(tài)代理的實現(xiàn)方法”的內(nèi)容了,經(jīng)過本文的學(xué)習(xí)后,相信大家對JDK動態(tài)代理的實現(xiàn)方法這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

jdk
AI