成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

原來這才是動態代理?。?!

開發 架構
動態代理的應用場景有很多,最常見的就是 AOP 的實現、RPC 遠程調用、Java 注解對象獲取、日志框架、全局性異常處理、事務處理等。

各位小伙伴們大家吼啊!我是 cxuan,距離上次更新已經有段時間了,臨近過年了,項目這邊也比較忙,而且最近很多時間都花在看書、提升自己上面,文章寫的比較拖沓,這里我要自我反思(其實我已經籌備了幾篇文章,就等結尾了,嘿嘿嘿)。

我們上篇文章聊了一波什么是動態代理,然后我又從動態代理的四種實現為切入點,為你講解 JDK 動態代理、CGLIB 動態代理、Javaassist、ASM 反向生成字節碼的區別,具體的內容你可以參見下面這篇文章。

動態代理竟然如此簡單!

那么這篇文章我們來聊一下動態代理的實現原理。

為了保險起見,我們首先花幾分鐘回顧一下什么是動態代理吧!

什么是動態代理

首先,動態代理是代理模式的一種實現方式,代理模式除了動態代理還有 靜態代理,只不過靜態代理能夠在編譯時期確定類的執行對象,而動態代理只有在運行時才能夠確定執行對象是誰。代理可以看作是對最終調用目標的一個封裝,我們能夠通過操作代理對象來調用目標類,這樣就可以實現調用者和目標對象的解耦合。

動態代理的應用場景有很多,最常見的就是 AOP 的實現、RPC 遠程調用、Java 注解對象獲取、日志框架、全局性異常處理、事務處理等。

動態代理的實現有很多,但是 JDK 動態代理是很重要的一種,下面我們就 JDK 動態代理來深入理解一波。

JDK 動態代理

首先我們先來看一下動態代理的執行過程

在 JDK 動態代理中,實現了 InvocationHandler的類可以看作是 代理類(因為類也是一種對象,所以我們上面為了描述關系,把代理類形容成了代理對象)。JDK 動態代理就是圍繞實現了 InvocationHandler 的代理類進行的,比如下面就是一個 InvocationHandler 的實現類,同時它也是一個代理類。

public class UserHandler implements InvocationHandler {

private UserDao userDao;

public UserHandler(UserDao userDao){
this.userDao = userDao;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
saveUserStart();
Object obj = method.invoke(userDao, args);
saveUserDone();
return obj;
}

public void saveUserStart(){
System.out.println("---- 開始插入 ----");
}

public void saveUserDone(){
System.out.println("---- 插入完成 ----");
}
}

代理類一個最最最重要的方法就是 invoke 方法,它有三個參數

  • Object proxy: 動態代理對象,關于這個方法我們后面會說。
  • Method method: 表示最終要執行的方法,method.invoke 用于執行被代理的方法,也就是真正的目標方法
  • Object[] args: 這個參數就是向目標方法傳遞的參數。

我們構造好了代理類,現在就要使用它來實現我們對目標對象的調用,那么如何操作呢?請看下面代碼

public static void dynamicProxy(){

UserDao userDao = new UserDaoImpl();
InvocationHandler handler = new UserHandler(userDao);

ClassLoader loader = userDao.getClass().getClassLoader();
Class<?>[] interfaces = userDao.getClass().getInterfaces();

UserDao proxy = (UserDao)Proxy.newProxyInstance(loader, interfaces, handler);
proxy.saveUser();
}

如果要用 JDK 動態代理的話,就需要知道目標對象的類加載器、目標對象的接口,當然還要知道目標對象是誰。構造完成后,我們就可以調用 Proxy.newProxyInstance方法,然后把類加載器、目標對象的接口、目標對象綁定上去就完事兒了。

這里需要注意一下 Proxy 類,它就是動態代理實現所用到的代理類。

Proxy 位于java.lang.reflect 包下,這同時也旁敲側擊的表明動態代理的本質就是反射。

下面我們就圍繞 JDK 動態代理,來深入理解一下它的原理,以及搞懂為什么動態代理的本質就是反射。

動態代理的實現原理

在了解動態代理的實現原理之前,我們先來了解一下 InvocationHandler 接口

InvocationHandler 接口

JavaDoc 告訴我們,InvocationHandler 是一個接口,實現這個接口的類就表示該類是一個代理實現類,也就是代理類。

InvocationHandler 接口中只有一個 invoke 方法。

動態代理的優勢在于能夠很方便的對代理類中方法進行集中處理,而不用修改每個被代理的方法。因為所有被代理的方法(真正執行的方法)都是通過在 InvocationHandler 中的 invoke 方法調用的。所以我們只需要對 invoke 方法進行集中處理。

invoke 方法只有三個參數

public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
  • proxy:代理對象
  • method: 代理對象調用的方法
  • args:調用方法中的參數。

動態代理的整個代理過程不像靜態代理那樣一目了然,清晰易懂,因為在動態代理的過程中,我們沒有看到代理類的真正代理過程,也不明白其具體操作,所以要分析動態代理的實現原理,我們必須借助源碼。

那么問題來了,首先第一步應該從哪分析?如果不知道如何分析的話,干脆就使用倒推法,從后往前找,我們直接先從 Proxy.newProxyInstance入手,看看是否能略知一二。

Proxy.newInstance 方法分析

Proxy 提供了創建動態代理類和實例的靜態方法,它也是由這些方法創建的所有動態代理類的超類。

public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
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);

try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}

final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (Exception e) {
...
}

乍一看起來有點麻煩,其實源碼都是這樣,看起來非常復雜,但是慢慢分析、厘清條理過后就好,最重要的是分析源碼不能著急。

上面這個 Proxy.newProxyInstsance 其實就做了下面幾件事,我畫了一個流程圖作為參考。

從上圖中我們也可以看出,newProxyInstsance 方法最重要的幾個環節就是獲得代理類、獲得構造器,然后構造新實例。

對反射有一些了解的同學,應該會知道獲得構造器和構造新實例是怎么回事。關于反射,可以參考筆者的這篇文章

學會反射后,我被錄取了!

所以我們的重點就放在了獲得代理類,這是最關鍵的一步,對應源碼中的 Class cl = getProxyClass0(loader, intfs); 我們進入這個方法一探究竟

private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
return proxyClassCache.get(loader, interfaces);
}

這個方法比較簡單,首先會直接判斷接口長度是否大于 65535(剛開始看到這里我是有點不明白的,我心想這個判斷是要判斷什么?interfaces 這不是一個 class 類型嗎,從 length 點進去也看不到這個屬性,細看一下才明白,這居然是可變參數,Class ... 中的 ... 就是可變參數,所以這個判斷我猜測應該是判斷接口數量是否大于 65535。)

然后會直接從 proxyClassCache 中根據 loader 和 interfaces 獲取代理對象實例。如果能夠根據 loader 和 interfaces 找到代理對象,將會返回緩存中的對象副本;否則,它將通過 ProxyClassFactory 創建代理類。

proxyClassCache.get 就是一系列從緩存中的查詢操作,注意這里的 proxyClassCache 其實是一個WeakCache,WeakCahe 也是位于 java.lang.reflect 包下的一個緩存映射 map,它的主要特點是一個弱引用的 map,但是它內部有一個 SubKey ,這個子鍵卻是強引用的。

這里我們不用去追究這個 proxyClassCache 是如何進行緩存的,只需要知道它的緩存時機就可以了:即在類加載的時候進行緩存。

如果無法找到代理對象,就會通過 ProxyClassFactory 創建代理,ProxyClassFactory 繼承于 BiFunction

private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{...}

ProxyClassFactory 里面有兩個屬性一個方法。

proxyClassNamePrefix:這個屬性表明使用 ProxyClassFactory 創建出來的代理實例的命名是以 "$Proxy" 為前綴的。

nextUniqueNumber:這個屬性表明 ProxyClassFactory 的后綴是使用 AtomicLong 生成的數字

所以代理實例的命名一般是 、Proxy1這種。

這個 apply 方法是一個根據接口和類加載器進行代理實例創建的工廠方法,下面是這段代碼的核心。

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

...

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

可以看到,代理實例的命名就是我們上面所描述的那種命名方式,只不過它這里加上了 proxyPkg 包名的路徑。然后下面就是生成代理實例的關鍵代碼。

ProxyGenerator.generateProxyClass 我們跟進去是只能看到 .class 文件的,class 文件是虛擬機編譯之后的結果,所以我們要看一下 .java 文件源碼。.java 源碼位于 OpenJDK中的 sun.misc 包中的 ProxyGenerator 下。

此類的 generateProxyClass() 靜態方法的核心內容就是去調用 generateClassFile() 實例方法來生成 Class 文件。方法太長了我們不貼了,這里就大致解釋以下其作用:

  • 第一步:收集所有要生成的代理方法,將其包裝成 ProxyMethod 對象并注冊到 Map 集合中。
  • 第二步:收集所有要為 Class 文件生成的字段信息和方法信息。
  • 第三步:完成了上面的工作后,開始組裝 Class 文件。

而 defineClass0 這個方法我們點進去是 native ,底層是 C/C++ 實現的,于是我又去看了一下 C/C++ 源碼,路徑在

點開之后的 C/C++ 源碼還是挺讓人絕望的。

不過我們再回頭看一下這個 defineClass0 方法,它實際上就是根據上面生成的 proxyClassFile 字節數組來生成對應的實例罷了,所以我們不必再深究 C/C++ 對于代理對象的合成過程了。

所以總結一下可以看出,JDK 為我們的生成了一個叫 $Proxy0 的代理類,這個類文件放在內存中的,我們在創建代理對象時,就是通過反射獲得這個類的構造方法,然后創建的代理實例。

所以最開始的 dynamicProxy 方法我們反編譯后的代碼就是這樣的

public final class $Proxy0 extends java.lang.reflect.Proxy implements com.cxuan.dynamic.UserDao {
public $Proxy0(java.lang.reflect.InvocationHandler) throws ;
Code:
0: aload_0
1: aload_1
2: invokespecial #8 // Method java/lang/reflect/Proxy."<init>":(Ljava/lang/reflect/InvocationHandler;)V
5: return

我們看到代理類繼承了 Proxy 類,所以也就決定了 Java 動態代理只能對接口進行代理。

于是,上面這個圖你應該就可以看懂了。

invoke 方法中第一個參數 proxy 的作用

細心的小伙伴們可能都發現了,invoke 方法中第一個 proxy 的作用是啥?代碼里面好像 proxy 也沒用到啊,這個參數的意義是啥呢?它運行時的類型是啥啊?為什么不使用 this 代替呢?

Stackoverflow 給出了我們一個回答 https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca

什么意思呢?

就是說這個 proxy ,它是真正的代理對象,invoke 方法可以返回調用代理對象方法的返回結果,也可以返回對象的真實代理對象,也就是 $Proxy0,這也是它運行時的類型。

至于為什么不用 this 來代替 proxy,因為實現了 InvocationHandler 的對象中的 this ,指代的還是 InvocationHandler 接口實現類本身,而不是真實的代理對象。

后記

另外,這段時間公眾號出了一些狀況,大家在公眾號回復的一些關鍵詞都沒有對應的連接了,這里和大家說明抱歉。


責任編輯:武曉燕 來源: 程序員cxua
相關推薦

2021-12-15 07:24:56

SocketTCPUDP

2024-06-03 09:52:08

2019-05-05 09:24:09

KafkaTopicPartition

2015-11-16 13:31:24

大數據騙局

2016-02-29 10:52:02

大數據數據基礎設施大數據應用

2021-06-21 09:36:44

微信語音轉發

2022-03-16 12:30:27

云服務器服務器

2013-11-28 14:34:30

微軟WP

2024-09-25 08:22:06

2020-05-28 10:45:31

Git分支合并

2016-12-16 19:06:02

擴展數據庫架構

2012-05-17 11:04:18

匈牙利命名法

2022-04-26 18:08:21

C語言代碼編程規范

2020-03-05 16:47:51

Git內部儲存

2021-02-28 13:52:46

程序員編碼技術

2024-12-06 12:17:31

2017-03-08 13:12:44

編程學習

2015-10-14 09:17:44

大可樂手機破產

2015-02-11 09:35:09

iPhone6

2019-01-02 10:49:54

Tomcat內存HotSpot VM
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 成人国产精品视频 | 国产福利91精品一区二区三区 | 日韩福利 | 亚洲高清在线 | 国产污视频在线 | 麻豆视频在线免费看 | 一区二区三区国产好 | 真人一级毛片 | 亚洲精品一区二区二区 | 在线视频h | 久久久久久久一区二区 | 超碰人人91| 亚洲午夜在线 | 免费在线观看一区二区三区 | 日本免费一区二区三区四区 | 精品在线免费观看视频 | 男人天堂网av | 欧美v日韩v| 亚洲高清一区二区三区 | 91网站在线看 | a级在线观看 | 亚洲欧美在线视频 | www.久| 国产精品国产精品国产专区不卡 | 亚洲狠狠丁香婷婷综合久久久 | 亚洲精品片| 亚洲一区二区三区免费视频 | 国产精品99久久久久久宅男 | www.久草.com | 久草影视在线 | 欧美精品欧美精品系列 | 欧美区日韩区 | 久久久久久久一区二区三区 | 免费黄色片在线观看 | 国产自产21区 | 国产成人一区二区 | 精品国产99 | 亚洲午夜精品在线观看 | 成人在线小视频 | 狠狠艹| 日韩亚洲欧美一区 |