反射(Reflect)
反射之中包含了一個(gè)「反」字,所以了解反射我們先從「正」開(kāi)始。
一般情況下,我們使用某個(gè)類時(shí)必定知道它是什么類,是用來(lái)做什么的。于是我們直接對(duì)這個(gè)類進(jìn)行實(shí)例化,之后使用這個(gè)類對(duì)象進(jìn)行操作。
反射則是一開(kāi)始并不知道我要初始化的類對(duì)象是什么,自然也無(wú)法使用 new 關(guān)鍵字來(lái)創(chuàng)建對(duì)象了。這時(shí)候,我們使用 JDK 提供的反射 API 進(jìn)行反射調(diào)用。反射就是在運(yùn)行時(shí)才知道要操作的類是什么,并且可以在運(yùn)行時(shí)獲取類的完整構(gòu)造,并調(diào)用對(duì)應(yīng)的方法。
Reflection(反射)是Java被視為動(dòng)態(tài)語(yǔ)言的關(guān)鍵,反射機(jī)制允許程序在執(zhí)行期借助于Reflection API取得任何類的內(nèi)部信息,并能直接操作任意對(duì)象的內(nèi)部屬性及方法。
Java反射機(jī)制主要提供了以下功能:
在運(yùn)行時(shí)構(gòu)造任意一個(gè)類的對(duì)象
在運(yùn)行時(shí)獲取任意一個(gè)類所具有的成員變量和方法
在運(yùn)行時(shí)調(diào)用任意一個(gè)對(duì)象的方法(屬性)
Java 是一門面向?qū)ο蟮恼Z(yǔ)言。在面向?qū)ο蟮氖澜缋?,萬(wàn)事萬(wàn)物皆對(duì)象,既然萬(wàn)事萬(wàn)物皆對(duì)象,那么我們的類是不是對(duì)象呢?我們寫的每一個(gè)類都可以看成一個(gè)對(duì)象,是 java.lang.Class 類的對(duì)象。每一個(gè)類對(duì)應(yīng)的Class放在哪里呢?當(dāng)我們寫完一個(gè)類的Java文件,編譯成class文件的時(shí)候,編譯器都會(huì)將這個(gè)類的對(duì)應(yīng)的class對(duì)象放在class文件的末尾。里面都保存了些什么?大家可以理解保存了類的元數(shù)據(jù)信息,一個(gè)類的元數(shù)據(jù)信息包括什么?有哪些屬性,方法,構(gòu)造器,實(shí)現(xiàn)了哪些接口等等,那么這些信息在Java里都有對(duì)應(yīng)的類來(lái)表示。
Class類
Class是一個(gè)類,封裝了當(dāng)前對(duì)象所對(duì)應(yīng)的類的信息
一個(gè)類中有屬性,方法,構(gòu)造器等,比如說(shuō)有一個(gè)Person類,一個(gè)Order類,一個(gè)Book類,這些都是不同的類,現(xiàn)在需要一個(gè)類,用來(lái)描述類,這就是Class,它應(yīng)該有類名,屬性,方法,構(gòu)造器等。Class是用來(lái)描述類的類。
Class類是一個(gè)對(duì)象照鏡子的結(jié)果,對(duì)象可以看到自己有哪些屬性,方法,構(gòu)造器,實(shí)現(xiàn)了哪些接口等等
對(duì)于每個(gè)類而言,JRE 都為其保留一個(gè)不變的 Class 類型的對(duì)象。一個(gè) Class 對(duì)象包含了特定某個(gè)類的有關(guān)信息。
對(duì)象只能由系統(tǒng)建立對(duì)象,一個(gè)類(而不是一個(gè)對(duì)象)在 JVM 中只會(huì)有一個(gè)Class實(shí)例
獲取Class對(duì)象的三種方式:
? ??1.通過(guò)類名獲取????? 類名.class???
????2.通過(guò)對(duì)象獲取?????對(duì)象名.getClass()
3.通過(guò)全類名獲取???Class.forName(全類名)
Class類的常用方法

類加載器、構(gòu)造器、Method、Field
參見(jiàn)包c(diǎn)n.enjoyedu.refle.more下對(duì)應(yīng)的類,評(píng)論問(wèn)我要

動(dòng)態(tài)代理
代理模式和靜態(tài)代理
代理模式給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象,并由代理對(duì)象控制對(duì)原對(duì)象的引用。通俗的來(lái)講代理模式就是我們生活中常見(jiàn)的中介。
舉個(gè)例子來(lái)說(shuō)明:張三想買某種用品,雖然他可以自己去找,但是這確實(shí)太浪費(fèi)時(shí)間和精力了,或者不好意思去買。于是張三就通過(guò)中介Mark來(lái)買,Mark來(lái)幫張三,張三只是負(fù)責(zé)選擇自己喜歡的的size,然后付錢就可以了。
目的:(1)通過(guò)引入代理對(duì)象的方式來(lái)間接訪問(wèn)目標(biāo)對(duì)象,防止直接訪問(wèn)目標(biāo)對(duì)象給系統(tǒng)帶來(lái)的不必要復(fù)雜性; (2)通過(guò)代理對(duì)象對(duì)原有的業(yè)務(wù)增強(qiáng);
代理模式一般會(huì)有三個(gè)角色:

抽象角色:指代理角色和真實(shí)角色對(duì)外提供的公共方法,一般為一個(gè)接口
真實(shí)角色:需要實(shí)現(xiàn)抽象角色接口,定義了真實(shí)角色所要實(shí)現(xiàn)的業(yè)務(wù)邏輯,以便供代理角色調(diào)用。也就是真正的業(yè)務(wù)邏輯在此。
代理角色:需要實(shí)現(xiàn)抽象角色接口,是真實(shí)角色的代理,通過(guò)真實(shí)角色的業(yè)務(wù)邏輯方法來(lái)實(shí)現(xiàn)抽象方法,并可以附加自己的操作。將統(tǒng)一的流程控制都放到代理角色中處理!
而訪問(wèn)者不再訪問(wèn)真實(shí)角色,而是去訪問(wèn)代理角色。
靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類。一般來(lái)說(shuō),被代理對(duì)象和代理對(duì)象是一對(duì)一的關(guān)系,當(dāng)然一個(gè)代理對(duì)象對(duì)應(yīng)多個(gè)被代理對(duì)象也是可以的。
靜態(tài)代理,一對(duì)一則會(huì)出現(xiàn)時(shí)靜態(tài)代理對(duì)象量多、代碼量大,從而導(dǎo)致代碼復(fù)雜,可維護(hù)性差的問(wèn)題,一對(duì)多則代理對(duì)象會(huì)出現(xiàn)擴(kuò)展能力差的問(wèn)題。
動(dòng)態(tài)代理
是指在使用時(shí)再創(chuàng)建代理類和實(shí)例
優(yōu)點(diǎn)
只需要1個(gè)動(dòng)態(tài)代理類就可以解決創(chuàng)建多個(gè)靜態(tài)代理的問(wèn)題,避免重復(fù)、多余代碼
更強(qiáng)的靈活性
缺點(diǎn)
效率低,相比靜態(tài)代理中 直接調(diào)用目標(biāo)對(duì)象方法,動(dòng)態(tài)代理則需要先通過(guò)Java反射機(jī)制 從而 間接調(diào)用目標(biāo)對(duì)象方法
應(yīng)用場(chǎng)景局限,因?yàn)?Java 的單繼承特性(每個(gè)代理類都繼承了 Proxy 類),即只能針對(duì)接口 創(chuàng)建 代理類,不能針對(duì)類創(chuàng)建代理類。
在java的動(dòng)態(tài)代理機(jī)制中,有兩個(gè)重要的類或接口,一個(gè)是InvocationHandler接口、另一個(gè)則是 Proxy類,這個(gè)類和接口是實(shí)現(xiàn)我們動(dòng)態(tài)代理所必須用到的。
InvocationHandler接口是給動(dòng)態(tài)代理類實(shí)現(xiàn)的,負(fù)責(zé)處理被代理對(duì)象的操作的,而Proxy是用來(lái)創(chuàng)建動(dòng)態(tài)代理類實(shí)例對(duì)象的,因?yàn)橹挥械玫搅诉@個(gè)對(duì)象我們才能調(diào)用那些需要代理的方法。
具體使用,參見(jiàn)包c(diǎn)n.enjoyedu.proxy.dynamic下MarkCompany類(評(píng)論找我要)
動(dòng)態(tài)代理實(shí)現(xiàn)原理
通過(guò)調(diào)試模式我們發(fā)現(xiàn),動(dòng)態(tài)代理里,代理類的類名是這樣的:

這個(gè)代理類為何是這個(gè)名字?它是如何執(zhí)行被代理對(duì)象的相關(guān)方法呢?我們?cè)趈ava文件編譯后的目錄里其實(shí)找不到這個(gè)名為$Proxy0的class文件的。
觀察Proxy.newProxyInstance方法,與創(chuàng)建對(duì)象有關(guān)的代碼主要有:
獲得代理類的class對(duì)象:

獲得代理類的構(gòu)造器:

創(chuàng)建代理類的實(shí)例

看來(lái)其中的關(guān)鍵點(diǎn)就是如何獲得代理類的class對(duì)象,我們進(jìn)入getProxyClass0方法,進(jìn)而進(jìn)入proxyClassCache.get方法,通過(guò)這個(gè)這個(gè)方法所在的類名,我們可以推測(cè),JDK內(nèi)部使用了某種機(jī)制緩存了我們的代理類的class對(duì)象,同時(shí)get方法接受的參數(shù)是被代理類的類加載器和類實(shí)現(xiàn)的的接口。
在這個(gè)get方法中,除去和緩存相關(guān)的操作,同時(shí)用到了被代理類的類加載器和類實(shí)現(xiàn)的的接口這兩個(gè)參數(shù)的是

我們?cè)龠M(jìn)入這個(gè)方法的實(shí)現(xiàn),


而最終生成代理類的class對(duì)象是defineClass0方法,但是這個(gè)方法是個(gè)native方法,所以我們不去也無(wú)法深究它,但是通過(guò)這個(gè)方法的參數(shù)我們可以明顯看到它接收了上面所生成的byte數(shù)組。
而我們通過(guò)ProxyUtils,這個(gè)自己寫的工具類,將這個(gè)byte數(shù)組寫入文件,我們并反編譯,我們將會(huì)看到

同時(shí)我們還會(huì)看到其中實(shí)現(xiàn)了業(yè)務(wù)接口的方法


而h則來(lái)自派生類Proxy中

這個(gè)h的實(shí)例來(lái)自哪里?不就是我們?cè)趧?chuàng)建代理類的實(shí)例時(shí)傳入的嗎?

使用了動(dòng)態(tài)代理的`Retrofit
Retrofit簡(jiǎn)單的說(shuō)就是一個(gè)網(wǎng)絡(luò)請(qǐng)求的適配器,它將一個(gè)基本的Java接口通過(guò)動(dòng)態(tài)代理的方式翻譯成一個(gè)HTTP請(qǐng)求,并通過(guò)OkHttp去發(fā)送請(qǐng)求。此外它還具有強(qiáng)大的可擴(kuò)展性,支持各種格式轉(zhuǎn)換以及RxJava。我們基于Retrofit2解析。
先定義一個(gè)名為X的java接口,當(dāng)然里面有各種注解。
@FormUrlEncoded注解表示from表單,另外還有@Multipart等注解。@POST表示post請(qǐng)求,此外還可以使用@GET請(qǐng)求

然后如何使用的呢?
首先將域名傳入構(gòu)造一個(gè)Retrofit,然后通過(guò)retrofit中的create方法傳入一個(gè)Java接口并得到一個(gè)x(當(dāng)然x這個(gè)對(duì)象是經(jīng)過(guò)處理了的)調(diào)用getPersonalListInfo(12)然后返回一個(gè)Call,最后這個(gè)Call調(diào)用了enqueue方法去異步請(qǐng)求http,這就是一個(gè)基本的Retrofit的網(wǎng)絡(luò)請(qǐng)求。Retrofit2中Call接口的默認(rèn)實(shí)現(xiàn)是OkHttpCall,它默認(rèn)使用OkHttp3作為底層http請(qǐng)求client。

我們只定義了一個(gè)接口X,并沒(méi)有實(shí)現(xiàn)這個(gè)接口,那么它是如何工作的呢?我們看看create方法的實(shí)現(xiàn)。
create()方法是個(gè)泛型方法,調(diào)用它時(shí)會(huì)返回一個(gè)范型T的對(duì)象,我們這里類型是X接口,在內(nèi)部實(shí)現(xiàn)上,很明顯了使用了動(dòng)態(tài)代理返回了一個(gè)X的代理類。當(dāng)調(diào)用X內(nèi)部方法的時(shí)候,會(huì)調(diào)用invoke方法。invoke方法內(nèi)則通過(guò)內(nèi)部一系列的封裝最后返回一個(gè)Call對(duì)象。

將一段Java代碼編譯成Class文件,然后再用字節(jié)碼反編譯工具進(jìn)行反編譯后,將會(huì)發(fā)現(xiàn)泛型都不見(jiàn)了,程序又變回了Java泛型出現(xiàn)之前的寫法,泛型類型都變回了原生類型

上面這段代碼是不能被編譯的,因?yàn)閰?shù)List<Integer>和List<String>編譯之后都被擦除了,變成了一樣的原生類型List<E>,擦除動(dòng)作導(dǎo)致這兩種方法的特征簽名變得一模一樣。
由于Java泛型的引入,各種場(chǎng)景(虛擬機(jī)解析、反射等)下的方法調(diào)用都可能對(duì)原有的基礎(chǔ)產(chǎn)生影響和新的需求,如在泛型類中如何獲取傳入的參數(shù)化類型等。因此,JCP組織對(duì)虛擬機(jī)規(guī)范做出了相應(yīng)的修改,引入了諸如Signature、LocalVariableTypeTable等新的屬性用于解決伴隨泛型而來(lái)的參數(shù)類型的識(shí)別問(wèn)題,Signature是其中最重要的一項(xiàng)屬性,它的作用就是存儲(chǔ)一個(gè)方法在字節(jié)碼層面的特征簽名[3],這個(gè)屬性中保存的參數(shù)類型并不是原生類型,而是包括了參數(shù)化類型的信息。修改后的虛擬機(jī)規(guī)范要求所有能識(shí)別49.0以上版本的Class文件的虛擬機(jī)都要能正確地識(shí)別Signature參數(shù)。
另外,從Signature屬性的出現(xiàn)我們還可以得出結(jié)論,擦除法所謂的擦除,僅僅是對(duì)方法的Code屬性中的字節(jié)碼進(jìn)行擦除,實(shí)際上元數(shù)據(jù)中還是保留了泛型信息,這也是我們能通過(guò)反射手段取得參數(shù)化類型的根本依據(jù)。
補(bǔ)充動(dòng)態(tài)代理實(shí)現(xiàn)
首先定義一個(gè)接口

然后寫他的實(shí)現(xiàn)

然后新建一個(gè)類實(shí)現(xiàn)InvocationHandler接口

最后調(diào)用
