91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

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

JDK動態代理的原理是什么

發布時間:2021-06-17 17:24:15 來源:億速云 閱讀:131 作者:Leah 欄目:大數據

JDK動態代理的原理是什么,針對這個問題,這篇文章詳細介紹了相對應的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

JDK動態代理使用與原理底層解析

java對設計模式--代理模式的實現,只能針對接口進行代理。代理模式:提供一個代理對象來持有目標對象的引用,通過對代理對象的操作可以達到操作目標對象的目的。使用代理模式主要是使用者不想或者不能直接操作目標對象,需要一個代理的中間對象來維持聯系。例如Mybatis中Mapper接口并沒有實現類,因此使用者不能直接操作實現類,所以會產生一個代理Mapper。又例如Spring AOP中的Bean,使用者想對Bean的使用進行增強或者其他處理,于是Spring需要返回一個的代理Bean來完成目的。

一接口:

public interface ITodo {
    void doString(String desc);
}

一實現:

public class Todo implements ITodo {
    @Override
    public void doString(String desc) {
        System.out.println("doString: " + desc);
    }
}

目標,對接口的原有方法進行增強。實現方式:JDK動態代理

一代理工具類:

public class ProxyInstance implements InvocationHandler {
    // 代理目標,即被代理類
    private Object target;

    // 代理類持有被代理類 
    public ProxyInstance(Object target) {
        this.target = target;
    }

    public <T> T getProxy(){
        // 獲取實例方式,這里使用newProxyInstance
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    // 這里為代理的處理流程
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("proxy before");
        Object obj=method.invoke(target,args);
        System.out.println("proxy after");
        return obj;
    }
}
測試類:
public class Test {
    public static void main(String[] args) {
        // 開啟保存代理中生成文件的代碼
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        ITodo todo = new ProxyInstance(new Todo()).getProxy();
        todo.doString("------ nothing-------- ");
    }
}
控制臺:
proxy before
doString: ------ nothing--------
proxy after

通過控制臺就可以看到接口原有的方法被增強了,方法執行之前和執行之后執行其他代碼。這就是JDK的動態代理功能。JDK動態代理的使用組件:一接口、一實現、一代理工具類,并且需要遵循以下規則:

代理工具類持有目標類
  • 作為參數傳入

代理工具類需要實現接口InvocationHandler
  • 代理類需要使用Proxy的構造方法獲取實例

    • 一般使用newProxyInstance

    • 定義代理處理邏輯

    • 用于通過反射生成代理類的方法

    • 因此只能代理接口中已定義的方法

    • 接口的所有方法都會被重寫為final類型

    • 用于加載新生成的代理類$Proxy{n}

    • 參數1: 類加載器

    • 參數2: 接口Class數組

    • 參數3: InvocationHandler接口實現類

  • 構造參數為被代理類實現的接口

  • 代理類調用的對象是Proxy類產生的實例,與被代理類不是一個對象

    • 每次調用都需要通過反射來調用

代理工具類需要重寫invoke方法
  • public Object invoke(Object proxy, Method method, Object[] args)

    • proxy為JDK生成的代理對象$Proxy{n}對象,因此是動態的,只存在于內存中

    • Method為JDK例如反射獲取的調用方法

    • args為調用方法的參數

    • 因為該類是生成的,所有需要類加載器從新加載


底層原理分析

對上面的示例,查看其生成的代理類源碼如下

package com.sun.proxy;


import cn.tinyice.demo.proxy.jdk.ITodo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;


// 代理對象集成java.lang.reflect.Proxy并實現了被代理對象的父接口
// 這里調整了方法位置,便于分析
public final class $Proxy0 extends Proxy implements ITodo {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

   static {
        // 需要對接口的equals、toString、hashCode和 目標方法進行重寫,因此先獲取原方法的運行時表示java.lang.reflect.Method
        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("cn.tinyice.demo.proxy.jdk.ITodo").getMethod("doString", 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());
        }
    }

    // 構造器入參:InvocationHandler實例即代理類實例
    public $Proxy0(InvocationHandler var1) throws  {
        //  判斷InvocationHandler不為空,然后賦給變量h
        super(var1);
    }

      // ----------------------------------------- 代理主要關注點: 目標方法 重寫 ------------------------
    public final void doString(String var1) throws  {
        try {
            //  super.h=InvocationHandler實例,調用其invoke方法,該方法為用戶重寫的方法
           // 三個參數來源確定: proxy=this,method = target的method,args=參數的Object數組(發生類型轉換)
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    // ----- equals、toString、hashCode 重寫,實質調用 InvocationHandler的對應方法-------
    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 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);
        }
    }
}

源碼部分主要分為四部分

代理類定義:

新生成的代理類定義為:public final class $Proxy0 extends Proxy implements ITodo {...},就是重新生成一個新的接口實現類,對原有實現類進行功能復制、增強。

靜態塊初始化equals、hashCode、toString和原有實現類的方法

equals、hashCode、toString這三個方法是Object方法,主要是驗證Java對象的唯一性,與原來的實現類已經不是一個內存地址了以及其他操作。

doString是原有實現類的方法

構造方法傳入InvocationHandler

InvocationHandler就是使用者編寫的代碼,這一步就是切入

重寫靜態塊中定義的所有方法

所有被重寫的方法都變為了final類型。所有的方法都調用了InvocationHandler的invoke方法。因此這個invoke方法就是增強的核心方法。

了解以上內容基本可以知道JDK動態代理的底層原理了。一句話:重寫方法調用自定義的invoke來實現增強


源碼生成流程

調用入口:獲取代理類

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { ... }

核心代嗎

Class<?> cl = getProxyClass0(loader, intfs);

proxyClassCache.get(loader, interfaces)

proxyClassCache在Proxy類中靜態定義

private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
proxyClassCache說明:
  • WeakCache實例,一個弱引用緩存對象。存儲結構如下,是 K,P,V三個對象存儲

ConcurrentMap<Object, ConcurrentMap<Object, Supplier<V>>> map = new ConcurrentHashMap<>();
  • 該對象需要2個工廠類,一個用于生成subKey,一個用于生成value

    • KeyFactory、ProxyClassFactory 均為BiFunction<ClassLoader, Class<?>[], Object>,具有apply方法

  • 獲取時和常規緩存使用方式一致,先通過key從緩存Map中獲取,獲取不到就去生成

    ConcurrentMap<Object, Supplier<V>

    。首次使用必是生成。

Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter)); 
// 這里生成subKey用于緩存的key
factory = new Factory(key, parameter, subKey, valuesMap); 
// 這個工廠是實現Supplier<V>Factory的get實現中調用valueFactory的apply即ProxyClassFactory // //#apply,并最終返回了value
value = Objects.requireNonNull(valueFactory.apply(key, parameter));

subkey只是中間的關聯變量,不需要關注,只需要關注value也就是代理類的生成。生成入口

ProxyClassFactory#apply(ClassLoader loader, Class<?>[] interfaces)

所有代理類的字節碼定義都在該方法中:主要如下

包名稱確定:
String proxyName = proxyPkg + proxyClassNamePrefix + num; // com.sun.proxy.+$Proxy+num --->com.sun.proxy.$Proxy0
  • proxyPkg在接口的訪問修飾符是public時=“com.sun.proxy“,否則=被代理類的包名

  • proxyClassNamePrefix=”$Proxy“

  • num為原子遞增AtomicLong,與生成代理類的個數相關

類字節碼生成:
  • 接口訪問修飾符 int accessFlags = Modifier.PUBLIC | Modifier.FINAL 即 public final

  • 非public接口會重寫為final

  • 所有方法都是 public final

類字節碼文件生成:
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
類字節碼加載到JVM:
defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);

此時新生成的代理類已經加載到JVM中去了


類字節碼文件生成

代理字節碼生成工具類 :ProxyGenerator

// 是否生成文件屬性讀取
private static final boolean saveGeneratedFiles = (Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"));


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;
}


字節碼生成,忽略部分代碼


private byte[] generateClassFile() {
    // equals、toString、hashCode 重寫
    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);
        }
    }
   // ... ignore ...
}

關于JDK動態代理的原理是什么問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

jdk
AI

和田县| 阿拉善右旗| 九寨沟县| 凤山市| 达日县| 绥宁县| 乌审旗| 榆林市| 宁城县| 临颍县| 吴旗县| 运城市| 东海县| 博爱县| 广宁县| 安龙县| 呈贡县| 改则县| 重庆市| 通道| 云阳县| 翁源县| 长垣县| 汝城县| 襄樊市| 平阴县| 酉阳| 祁东县| 肇州县| 镇宁| 五华县| 河东区| 醴陵市| 涿鹿县| 临朐县| 嫩江县| 梁平县| 兴仁县| 武功县| 屯留县| 铁岭市|