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

原來(lái)這才是動(dòng)態(tài)代理?。?!

開(kāi)發(fā) 架構(gòu)
動(dòng)態(tài)代理的應(yīng)用場(chǎng)景有很多,最常見(jiàn)的就是 AOP 的實(shí)現(xiàn)、RPC 遠(yuǎn)程調(diào)用、Java 注解對(duì)象獲取、日志框架、全局性異常處理、事務(wù)處理等。

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

我們上篇文章聊了一波什么是動(dòng)態(tài)代理,然后我又從動(dòng)態(tài)代理的四種實(shí)現(xiàn)為切入點(diǎn),為你講解 JDK 動(dòng)態(tài)代理、CGLIB 動(dòng)態(tài)代理、Javaassist、ASM 反向生成字節(jié)碼的區(qū)別,具體的內(nèi)容你可以參見(jiàn)下面這篇文章。

動(dòng)態(tài)代理竟然如此簡(jiǎn)單!

那么這篇文章我們來(lái)聊一下動(dòng)態(tài)代理的實(shí)現(xiàn)原理。

為了保險(xiǎn)起見(jiàn),我們首先花幾分鐘回顧一下什么是動(dòng)態(tài)代理吧!

什么是動(dòng)態(tài)代理

首先,動(dòng)態(tài)代理是代理模式的一種實(shí)現(xiàn)方式,代理模式除了動(dòng)態(tài)代理還有 靜態(tài)代理,只不過(guò)靜態(tài)代理能夠在編譯時(shí)期確定類(lèi)的執(zhí)行對(duì)象,而動(dòng)態(tài)代理只有在運(yùn)行時(shí)才能夠確定執(zhí)行對(duì)象是誰(shuí)。代理可以看作是對(duì)最終調(diào)用目標(biāo)的一個(gè)封裝,我們能夠通過(guò)操作代理對(duì)象來(lái)調(diào)用目標(biāo)類(lèi),這樣就可以實(shí)現(xiàn)調(diào)用者和目標(biāo)對(duì)象的解耦合。

動(dòng)態(tài)代理的應(yīng)用場(chǎng)景有很多,最常見(jiàn)的就是 AOP 的實(shí)現(xiàn)、RPC 遠(yuǎn)程調(diào)用、Java 注解對(duì)象獲取、日志框架、全局性異常處理、事務(wù)處理等。

動(dòng)態(tài)代理的實(shí)現(xiàn)有很多,但是 JDK 動(dòng)態(tài)代理是很重要的一種,下面我們就 JDK 動(dòng)態(tài)代理來(lái)深入理解一波。

JDK 動(dòng)態(tài)代理

首先我們先來(lái)看一下動(dòng)態(tài)代理的執(zhí)行過(guò)程

在 JDK 動(dòng)態(tài)代理中,實(shí)現(xiàn)了 InvocationHandler的類(lèi)可以看作是 代理類(lèi)(因?yàn)轭?lèi)也是一種對(duì)象,所以我們上面為了描述關(guān)系,把代理類(lèi)形容成了代理對(duì)象)。JDK 動(dòng)態(tài)代理就是圍繞實(shí)現(xiàn)了 InvocationHandler 的代理類(lèi)進(jìn)行的,比如下面就是一個(gè) InvocationHandler 的實(shí)現(xiàn)類(lèi),同時(shí)它也是一個(gè)代理類(lèi)。

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("---- 開(kāi)始插入 ----");
}

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

代理類(lèi)一個(gè)最最最重要的方法就是 invoke 方法,它有三個(gè)參數(shù)

  • Object proxy: 動(dòng)態(tài)代理對(duì)象,關(guān)于這個(gè)方法我們后面會(huì)說(shuō)。
  • Method method: 表示最終要執(zhí)行的方法,method.invoke 用于執(zhí)行被代理的方法,也就是真正的目標(biāo)方法
  • Object[] args: 這個(gè)參數(shù)就是向目標(biāo)方法傳遞的參數(shù)。

我們構(gòu)造好了代理類(lèi),現(xiàn)在就要使用它來(lái)實(shí)現(xiàn)我們對(duì)目標(biāo)對(duì)象的調(diào)用,那么如何操作呢?請(qǐng)看下面代碼

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 動(dòng)態(tài)代理的話(huà),就需要知道目標(biāo)對(duì)象的類(lèi)加載器、目標(biāo)對(duì)象的接口,當(dāng)然還要知道目標(biāo)對(duì)象是誰(shuí)。構(gòu)造完成后,我們就可以調(diào)用 Proxy.newProxyInstance方法,然后把類(lèi)加載器、目標(biāo)對(duì)象的接口、目標(biāo)對(duì)象綁定上去就完事兒了。

這里需要注意一下 Proxy 類(lèi),它就是動(dòng)態(tài)代理實(shí)現(xiàn)所用到的代理類(lèi)。

Proxy 位于java.lang.reflect 包下,這同時(shí)也旁敲側(cè)擊的表明動(dòng)態(tài)代理的本質(zhì)就是反射。

下面我們就圍繞 JDK 動(dòng)態(tài)代理,來(lái)深入理解一下它的原理,以及搞懂為什么動(dòng)態(tài)代理的本質(zhì)就是反射。

動(dòng)態(tài)代理的實(shí)現(xiàn)原理

在了解動(dòng)態(tài)代理的實(shí)現(xiàn)原理之前,我們先來(lái)了解一下 InvocationHandler 接口

InvocationHandler 接口

JavaDoc 告訴我們,InvocationHandler 是一個(gè)接口,實(shí)現(xiàn)這個(gè)接口的類(lèi)就表示該類(lèi)是一個(gè)代理實(shí)現(xiàn)類(lèi),也就是代理類(lèi)。

InvocationHandler 接口中只有一個(gè) invoke 方法。

動(dòng)態(tài)代理的優(yōu)勢(shì)在于能夠很方便的對(duì)代理類(lèi)中方法進(jìn)行集中處理,而不用修改每個(gè)被代理的方法。因?yàn)樗斜淮淼姆椒?真正執(zhí)行的方法)都是通過(guò)在 InvocationHandler 中的 invoke 方法調(diào)用的。所以我們只需要對(duì) invoke 方法進(jìn)行集中處理。

invoke 方法只有三個(gè)參數(shù)

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

動(dòng)態(tài)代理的整個(gè)代理過(guò)程不像靜態(tài)代理那樣一目了然,清晰易懂,因?yàn)樵趧?dòng)態(tài)代理的過(guò)程中,我們沒(méi)有看到代理類(lèi)的真正代理過(guò)程,也不明白其具體操作,所以要分析動(dòng)態(tài)代理的實(shí)現(xiàn)原理,我們必須借助源碼。

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

Proxy.newInstance 方法分析

Proxy 提供了創(chuàng)建動(dòng)態(tài)代理類(lèi)和實(shí)例的靜態(tài)方法,它也是由這些方法創(chuàng)建的所有動(dòng)態(tài)代理類(lèi)的超類(lèi)。

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) {
...
}

乍一看起來(lái)有點(diǎn)麻煩,其實(shí)源碼都是這樣,看起來(lái)非常復(fù)雜,但是慢慢分析、厘清條理過(guò)后就好,最重要的是分析源碼不能著急。

上面這個(gè) Proxy.newProxyInstsance 其實(shí)就做了下面幾件事,我畫(huà)了一個(gè)流程圖作為參考。

從上圖中我們也可以看出,newProxyInstsance 方法最重要的幾個(gè)環(huán)節(jié)就是獲得代理類(lèi)、獲得構(gòu)造器,然后構(gòu)造新實(shí)例。

對(duì)反射有一些了解的同學(xué),應(yīng)該會(huì)知道獲得構(gòu)造器和構(gòu)造新實(shí)例是怎么回事。關(guān)于反射,可以參考筆者的這篇文章

學(xué)會(huì)反射后,我被錄取了!

所以我們的重點(diǎn)就放在了獲得代理類(lèi),這是最關(guān)鍵的一步,對(duì)應(yīng)源碼中的 Class cl = getProxyClass0(loader, intfs); 我們進(jìn)入這個(gè)方法一探究竟

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

這個(gè)方法比較簡(jiǎn)單,首先會(huì)直接判斷接口長(zhǎng)度是否大于 65535(剛開(kāi)始看到這里我是有點(diǎn)不明白的,我心想這個(gè)判斷是要判斷什么?interfaces 這不是一個(gè) class 類(lèi)型嗎,從 length 點(diǎn)進(jìn)去也看不到這個(gè)屬性,細(xì)看一下才明白,這居然是可變參數(shù),Class ... 中的 ... 就是可變參數(shù),所以這個(gè)判斷我猜測(cè)應(yīng)該是判斷接口數(shù)量是否大于 65535。)

然后會(huì)直接從 proxyClassCache 中根據(jù) loader 和 interfaces 獲取代理對(duì)象實(shí)例。如果能夠根據(jù) loader 和 interfaces 找到代理對(duì)象,將會(huì)返回緩存中的對(duì)象副本;否則,它將通過(guò) ProxyClassFactory 創(chuàng)建代理類(lèi)。

proxyClassCache.get 就是一系列從緩存中的查詢(xún)操作,注意這里的 proxyClassCache 其實(shí)是一個(gè)WeakCache,WeakCahe 也是位于 java.lang.reflect 包下的一個(gè)緩存映射 map,它的主要特點(diǎn)是一個(gè)弱引用的 map,但是它內(nèi)部有一個(gè) SubKey ,這個(gè)子鍵卻是強(qiáng)引用的。

這里我們不用去追究這個(gè) proxyClassCache 是如何進(jìn)行緩存的,只需要知道它的緩存時(shí)機(jī)就可以了:即在類(lèi)加載的時(shí)候進(jìn)行緩存。

如果無(wú)法找到代理對(duì)象,就會(huì)通過(guò) ProxyClassFactory 創(chuàng)建代理,ProxyClassFactory 繼承于 BiFunction

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

ProxyClassFactory 里面有兩個(gè)屬性一個(gè)方法。

proxyClassNamePrefix:這個(gè)屬性表明使用 ProxyClassFactory 創(chuàng)建出來(lái)的代理實(shí)例的命名是以 "$Proxy" 為前綴的。

nextUniqueNumber:這個(gè)屬性表明 ProxyClassFactory 的后綴是使用 AtomicLong 生成的數(shù)字

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

這個(gè) apply 方法是一個(gè)根據(jù)接口和類(lèi)加載器進(jìn)行代理實(shí)例創(chuàng)建的工廠方法,下面是這段代碼的核心。

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

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

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

此類(lèi)的 generateProxyClass() 靜態(tài)方法的核心內(nèi)容就是去調(diào)用 generateClassFile() 實(shí)例方法來(lái)生成 Class 文件。方法太長(zhǎng)了我們不貼了,這里就大致解釋以下其作用:

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

而 defineClass0 這個(gè)方法我們點(diǎn)進(jìn)去是 native ,底層是 C/C++ 實(shí)現(xiàn)的,于是我又去看了一下 C/C++ 源碼,路徑在

點(diǎn)開(kāi)之后的 C/C++ 源碼還是挺讓人絕望的。

不過(guò)我們?cè)倩仡^看一下這個(gè) defineClass0 方法,它實(shí)際上就是根據(jù)上面生成的 proxyClassFile 字節(jié)數(shù)組來(lái)生成對(duì)應(yīng)的實(shí)例罷了,所以我們不必再深究 C/C++ 對(duì)于代理對(duì)象的合成過(guò)程了。

所以總結(jié)一下可以看出,JDK 為我們的生成了一個(gè)叫 $Proxy0 的代理類(lèi),這個(gè)類(lèi)文件放在內(nèi)存中的,我們?cè)趧?chuàng)建代理對(duì)象時(shí),就是通過(guò)反射獲得這個(gè)類(lèi)的構(gòu)造方法,然后創(chuàng)建的代理實(shí)例。

所以最開(kāi)始的 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

我們看到代理類(lèi)繼承了 Proxy 類(lèi),所以也就決定了 Java 動(dòng)態(tài)代理只能對(duì)接口進(jìn)行代理。

于是,上面這個(gè)圖你應(yīng)該就可以看懂了。

invoke 方法中第一個(gè)參數(shù) proxy 的作用

細(xì)心的小伙伴們可能都發(fā)現(xiàn)了,invoke 方法中第一個(gè) proxy 的作用是啥?代碼里面好像 proxy 也沒(méi)用到啊,這個(gè)參數(shù)的意義是啥呢?它運(yùn)行時(shí)的類(lèi)型是啥啊?為什么不使用 this 代替呢?

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

什么意思呢?

就是說(shuō)這個(gè) proxy ,它是真正的代理對(duì)象,invoke 方法可以返回調(diào)用代理對(duì)象方法的返回結(jié)果,也可以返回對(duì)象的真實(shí)代理對(duì)象,也就是 $Proxy0,這也是它運(yùn)行時(shí)的類(lèi)型。

至于為什么不用 this 來(lái)代替 proxy,因?yàn)閷?shí)現(xiàn)了 InvocationHandler 的對(duì)象中的 this ,指代的還是 InvocationHandler 接口實(shí)現(xiàn)類(lèi)本身,而不是真實(shí)的代理對(duì)象。

后記

另外,這段時(shí)間公眾號(hào)出了一些狀況,大家在公眾號(hào)回復(fù)的一些關(guān)鍵詞都沒(méi)有對(duì)應(yīng)的連接了,這里和大家說(shuō)明抱歉。


責(zé)任編輯:武曉燕 來(lái)源: 程序員cxua
相關(guān)推薦

2021-12-15 07:24:56

SocketTCPUDP

2024-06-03 09:52:08

2015-11-16 13:31:24

大數(shù)據(jù)騙局

2019-05-05 09:24:09

KafkaTopicPartition

2016-02-29 10:52:02

大數(shù)據(jù)數(shù)據(jù)基礎(chǔ)設(shè)施大數(shù)據(jù)應(yīng)用

2021-06-21 09:36:44

微信語(yǔ)音轉(zhuǎn)發(fā)

2022-03-16 12:30:27

云服務(wù)器服務(wù)器

2013-11-28 14:34:30

微軟WP

2020-05-28 10:45:31

Git分支合并

2016-12-16 19:06:02

擴(kuò)展數(shù)據(jù)庫(kù)架構(gòu)

2012-05-17 11:04:18

匈牙利命名法

2024-09-25 08:22:06

2015-10-14 09:17:44

大可樂(lè)手機(jī)破產(chǎn)

2015-02-11 09:35:09

iPhone6

2019-01-02 10:49:54

Tomcat內(nèi)存HotSpot VM

2022-04-26 18:08:21

C語(yǔ)言代碼編程規(guī)范

2020-03-05 16:47:51

Git內(nèi)部儲(chǔ)存

2021-02-28 13:52:46

程序員編碼技術(shù)

2017-03-08 13:12:44

編程學(xué)習(xí)

2024-12-06 12:17:31

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产.com| 亚洲人人 | 日韩成人影院在线观看 | 人人艹人人爽 | 狠狠涩| 久草综合在线视频 | 亚洲精品自拍 | 国产精品久久久久一区二区三区 | 成人av观看| 国产精品久久久久久久久久久免费看 | 亚洲一区二区三区在线播放 | 中文字幕久久精品 | 久久久久久久综合色一本 | 狠狠亚洲 | 国产成人区 | 日本欧美在线观看视频 | 色一情一乱一伦一区二区三区 | 日韩在线国产 | 黄网免费 | 欧美极品视频在线观看 | 欧美在线一区二区三区 | 久久a久久 | 国产精品区一区二区三 | 国产主播第一页 | 亚洲一区二区三区四区五区午夜 | 99国产精品99久久久久久 | 91精品国产91久久久久久 | 亚洲 中文 欧美 日韩 在线观看 | 伊人久久免费视频 | 日韩在线精品 | 久久久精品日本 | 欧美一区二区三区 | 日韩精品免费视频 | 99精品在线免费观看 | 亚洲三级在线观看 | av在线成人 | 99pao成人国产永久免费视频 | 亚洲第一av网站 | 中文字幕 亚洲一区 | 天天干夜夜操 | 久久伊人精品 |