簡(jiǎn)介
什么是反射
反射(Reflection)是 Java 程序開(kāi)發(fā)語(yǔ)言的特征之一误窖,它允許運(yùn)行中的 Java 程序獲取自身的信息隙畜,并且可以操作類或?qū)ο蟮膬?nèi)部屬性棚贾。
通過(guò)反射機(jī)制逞力,可以在運(yùn)行時(shí)訪問(wèn) Java 對(duì)象的屬性锅论,方法祈纯,構(gòu)造方法等令宿。
反射的應(yīng)用場(chǎng)景
反射的主要應(yīng)用場(chǎng)景有:
- 開(kāi)發(fā)通用框架 - 反射最重要的用途就是開(kāi)發(fā)各種通用框架。很多框架(比如 Spring)都是配置化的(比如通過(guò) XML 文件配置 JavaBean腕窥、Filter 等)粒没,為了保證框架的通用性,它們可能需要根據(jù)配置文件加載不同的對(duì)象或類簇爆,調(diào)用不同的方法癞松,這個(gè)時(shí)候就必須用到反射——運(yùn)行時(shí)動(dòng)態(tài)加載需要加載的對(duì)象。
- 動(dòng)態(tài)代理 - 在切面編程(AOP)中入蛆,需要攔截特定的方法响蓉,通常,會(huì)選擇動(dòng)態(tài)代理方式哨毁。這時(shí)枫甲,就需要反射技術(shù)來(lái)實(shí)現(xiàn)了。
- 注解 - 注解本身僅僅是起到標(biāo)記作用扼褪,它需要利用反射機(jī)制想幻,根據(jù)注解標(biāo)記去調(diào)用注解解釋器,執(zhí)行行為话浇。如果沒(méi)有反射機(jī)制脏毯,注解并不比注釋更有用。
- 可擴(kuò)展性功能 - 應(yīng)用程序可以通過(guò)使用完全限定名稱創(chuàng)建可擴(kuò)展性對(duì)象實(shí)例來(lái)使用外部的用戶定義類凳枝。
反射的缺點(diǎn)
- 性能開(kāi)銷 - 由于反射涉及動(dòng)態(tài)解析的類型抄沮,因此無(wú)法執(zhí)行某些 Java 虛擬機(jī)優(yōu)化跋核。因此,反射操作的性能要比非反射操作的性能要差叛买,應(yīng)該在性能敏感的應(yīng)用程序中頻繁調(diào)用的代碼段中避免砂代。
- 破壞封裝性 - 反射調(diào)用方法時(shí)可以忽略權(quán)限檢查,因此可能會(huì)破壞封裝性而導(dǎo)致安全問(wèn)題率挣。
- 內(nèi)部曝光 - 由于反射允許代碼執(zhí)行在非反射代碼中非法的操作刻伊,例如訪問(wèn)私有字段和方法,所以反射的使用可能會(huì)導(dǎo)致意想不到的副作用椒功,這可能會(huì)導(dǎo)致代碼功能失常并可能破壞可移植性捶箱。反射代碼打破了抽象,因此可能會(huì)隨著平臺(tái)的升級(jí)而改變行為动漾。
反射機(jī)制
類加載過(guò)程
類加載的完整過(guò)程如下:
(1)在編譯時(shí)丁屎,Java 編譯器編譯好 .java
文件之后,在磁盤中產(chǎn)生 .class
文件旱眯。.class
文件是二進(jìn)制文件晨川,內(nèi)容是只有 JVM 能夠識(shí)別的機(jī)器碼。
(2)JVM 中的類加載器讀取字節(jié)碼文件删豺,取出二進(jìn)制數(shù)據(jù)共虑,加載到內(nèi)存中,解析.class 文件內(nèi)的信息呀页。類加載器會(huì)根據(jù)類的全限定名來(lái)獲取此類的二進(jìn)制字節(jié)流妈拌;然后,將字節(jié)流所代表的靜態(tài)存儲(chǔ)結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)蓬蝶;接著尘分,在內(nèi)存中生成代表這個(gè)類的 java.lang.Class
對(duì)象。
(3)加載結(jié)束后疾党,JVM 開(kāi)始進(jìn)行連接階段(包含驗(yàn)證音诫、準(zhǔn)備惨奕、初始化)雪位。經(jīng)過(guò)這一系列操作,類的變量會(huì)被初始化梨撞。
Class 對(duì)象
要想使用反射雹洗,首先需要獲得待操作的類所對(duì)應(yīng)的 Class 對(duì)象。Java 中卧波,無(wú)論生成某個(gè)類的多少個(gè)對(duì)象时肿,這些對(duì)象都會(huì)對(duì)應(yīng)于同一個(gè) Class 對(duì)象。這個(gè) Class 對(duì)象是由 JVM 生成的港粱,通過(guò)它能夠獲悉整個(gè)類的結(jié)構(gòu)螃成。所以旦签,java.lang.Class
可以視為所有反射 API 的入口點(diǎn)。
反射的本質(zhì)就是:在運(yùn)行時(shí)寸宏,把 Java 類中的各種成分映射成一個(gè)個(gè)的 Java 對(duì)象宁炫。
舉例來(lái)說(shuō),假如定義了以下代碼:
User user = new User();
步驟說(shuō)明:
- JVM 加載方法的時(shí)候氮凝,遇到
new User()
羔巢,JVM 會(huì)根據(jù)User
的全限定名去加載User.class
。 - JVM 會(huì)去本地磁盤查找
User.class
文件并加載 JVM 內(nèi)存中罩阵。 - JVM 通過(guò)調(diào)用類加載器自動(dòng)創(chuàng)建這個(gè)類對(duì)應(yīng)的
Class
對(duì)象竿秆,并且存儲(chǔ)在 JVM 的方法區(qū)。注意:一個(gè)類有且只有一個(gè)Class
對(duì)象稿壁。
使用反射
java.lang.reflect 包
Java 中的 java.lang.reflect
包提供了反射功能幽钢。java.lang.reflect
包中的類都沒(méi)有 public
構(gòu)造方法。
java.lang.reflect
包的核心接口和類如下:
-
Member
接口 - 反映關(guān)于單個(gè)成員(字段或方法)或構(gòu)造函數(shù)的標(biāo)識(shí)信息傅是。 -
Field
類 - 提供一個(gè)類的域的信息以及訪問(wèn)類的域的接口搅吁。 -
Method
類 - 提供一個(gè)類的方法的信息以及訪問(wèn)類的方法的接口。 -
Constructor
類 - 提供一個(gè)類的構(gòu)造函數(shù)的信息以及訪問(wèn)類的構(gòu)造函數(shù)的接口落午。 -
Array
類 - 該類提供動(dòng)態(tài)地生成和訪問(wèn) JAVA 數(shù)組的方法谎懦。 -
Modifier
類 - 提供了 static 方法和常量,對(duì)類和成員訪問(wèn)修飾符進(jìn)行解碼溃斋。 -
Proxy
類 - 提供動(dòng)態(tài)地生成代理類和類實(shí)例的靜態(tài)方法界拦。
獲得 Class 對(duì)象
獲得 Class 的三種方法:
(1)使用 Class 類的 forName
靜態(tài)方法
示例:
package io.github.dunwu.javacore.reflect;
public class ReflectClassDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01");
System.out.println(c1.getCanonicalName());
Class c2 = Class.forName("[D");
System.out.println(c2.getCanonicalName());
Class c3 = Class.forName("[[Ljava.lang.String;");
System.out.println(c3.getCanonicalName());
}
}
//Output:
//io.github.dunwu.javacore.reflect.ReflectClassDemo01
//double[]
//java.lang.String[][]
使用類的完全限定名來(lái)反射對(duì)象的類。常見(jiàn)的應(yīng)用場(chǎng)景為:在 JDBC 開(kāi)發(fā)中常用此方法加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)梗劫。
(2)直接獲取某一個(gè)對(duì)象的 class
示例:
public class ReflectClassDemo02 {
public static void main(String[] args) {
boolean b;
// Class c = b.getClass(); // 編譯錯(cuò)誤
Class c1 = boolean.class;
System.out.println(c1.getCanonicalName());
Class c2 = java.io.PrintStream.class;
System.out.println(c2.getCanonicalName());
Class c3 = int[][][].class;
System.out.println(c3.getCanonicalName());
}
}
//Output:
//boolean
//java.io.PrintStream
//int[][][]
(3)調(diào)用 Object 的 getClass
方法享甸,示例:
Object 類中有 getClass 方法,因?yàn)樗蓄惗祭^承 Object 類梳侨。從而調(diào)用 Object 類來(lái)獲取
示例:
package io.github.dunwu.javacore.reflect;
import java.util.HashSet;
import java.util.Set;
public class ReflectClassDemo03 {
enum E {A, B}
public static void main(String[] args) {
Class c = "foo".getClass();
System.out.println(c.getCanonicalName());
Class c2 = ReflectClassDemo03.E.A.getClass();
System.out.println(c2.getCanonicalName());
byte[] bytes = new byte[1024];
Class c3 = bytes.getClass();
System.out.println(c3.getCanonicalName());
Set<String> set = new HashSet<>();
Class c4 = set.getClass();
System.out.println(c4.getCanonicalName());
}
}
//Output:
//java.lang.String
//io.github.dunwu.javacore.reflect.ReflectClassDemo.E
//byte[]
//java.util.HashSet
判斷是否為某個(gè)類的實(shí)例
判斷是否為某個(gè)類的實(shí)例有兩種方式:
- 用
instanceof
關(guān)鍵字 -
用
Class
對(duì)象的isInstance
方法(它是一個(gè) Native 方法)
示例:
public class InstanceofDemo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
if (arrayList instanceof List) {
System.out.println("ArrayList is List");
}
if (List.class.isInstance(arrayList)) {
System.out.println("ArrayList is List");
}
}
}
//Output:
//ArrayList is List
//ArrayList is List
創(chuàng)建實(shí)例
通過(guò)反射來(lái)創(chuàng)建實(shí)例對(duì)象主要有兩種方式:
- 用
Class
對(duì)象的newInstance
方法蛉威。 - 用
Constructor
對(duì)象的newInstance
方法。
示例:
public class NewInstanceDemo {
public static void main(String[] args)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c1 = StringBuilder.class;
StringBuilder sb = (StringBuilder) c1.newInstance();
sb.append("aaa");
System.out.println(sb.toString());
//獲取String所對(duì)應(yīng)的Class對(duì)象
Class<?> c2 = String.class;
//獲取String類帶一個(gè)String參數(shù)的構(gòu)造器
Constructor constructor = c2.getConstructor(String.class);
//根據(jù)構(gòu)造器創(chuàng)建實(shí)例
String str2 = (String) constructor.newInstance("bbb");
System.out.println(str2);
}
}
//Output:
//aaa
//bbb
Field
Class
對(duì)象提供以下方法獲取對(duì)象的成員(Field
):
-
getFiled
- 根據(jù)名稱獲取公有的(public)類成員走哺。 -
getDeclaredField
- 根據(jù)名稱獲取已聲明的類成員蚯嫌。但不能得到其父類的類成員。 -
getFields
- 獲取所有公有的(public)類成員丙躏。 -
getDeclaredFields
- 獲取所有已聲明的類成員择示。
示例如下:
public class ReflectFieldDemo {
class FieldSpy<T> {
public boolean[][] b = {{false, false}, {true, true}};
public String name = "Alice";
public List<Integer> list;
public T val;
}
public static void main(String[] args) throws NoSuchFieldException {
Field f1 = FieldSpy.class.getField("b");
System.out.format("Type: %s%n", f1.getType());
Field f2 = FieldSpy.class.getField("name");
System.out.format("Type: %s%n", f2.getType());
Field f3 = FieldSpy.class.getField("list");
System.out.format("Type: %s%n", f3.getType());
Field f4 = FieldSpy.class.getField("val");
System.out.format("Type: %s%n", f4.getType());
}
}
//Output:
//Type: class [[Z
//Type: class java.lang.String
//Type: interface java.util.List
//Type: class java.lang.Object
Method
Class
對(duì)象提供以下方法獲取對(duì)象的方法(Method
):
-
getMethod
- 返回類或接口的特定方法。其中第一個(gè)參數(shù)為方法名稱晒旅,后面的參數(shù)為方法參數(shù)對(duì)應(yīng) Class 的對(duì)象栅盲。 -
getDeclaredMethod
- 返回類或接口的特定聲明方法。其中第一個(gè)參數(shù)為方法名稱废恋,后面的參數(shù)為方法參數(shù)對(duì)應(yīng) Class 的對(duì)象谈秫。 -
getMethods
- 返回類或接口的所有 public 方法扒寄,包括其父類的 public 方法。 -
getDeclaredMethods
- 返回類或接口聲明的所有方法拟烫,包括 public旗们、protected、默認(rèn)(包)訪問(wèn)和 private 方法构灸,但不包括繼承的方法上渴。
獲取一個(gè) Method
對(duì)象后,可以用 invoke
方法來(lái)調(diào)用這個(gè)方法喜颁。
invoke
方法的原型為:
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
示例:
public class ReflectMethodDemo {
public static void main(String[] args)
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 返回所有方法
Method[] methods1 = System.class.getDeclaredMethods();
System.out.println("System getDeclaredMethods 清單(數(shù)量 = " + methods1.length + "):");
for (Method m : methods1) {
System.out.println(m);
}
// 返回所有 public 方法
Method[] methods2 = System.class.getMethods();
System.out.println("System getMethods 清單(數(shù)量 = " + methods2.length + "):");
for (Method m : methods2) {
System.out.println(m);
}
// 利用 Method 的 invoke 方法調(diào)用 System.currentTimeMillis()
Method method = System.class.getMethod("currentTimeMillis");
System.out.println(method);
System.out.println(method.invoke(null));
}
}
Constructor
Class
對(duì)象提供以下方法獲取對(duì)象的構(gòu)造方法(Constructor
):
-
getConstructor
- 返回類的特定 public 構(gòu)造方法稠氮。參數(shù)為方法參數(shù)對(duì)應(yīng) Class 的對(duì)象。 -
getDeclaredConstructor
- 返回類的特定構(gòu)造方法半开。參數(shù)為方法參數(shù)對(duì)應(yīng) Class 的對(duì)象隔披。 -
getConstructors
- 返回類的所有 public 構(gòu)造方法。 -
getDeclaredConstructors
- 返回類的所有構(gòu)造方法寂拆。
獲取一個(gè) Constructor
對(duì)象后奢米,可以用 newInstance
方法來(lái)創(chuàng)建類實(shí)例。
示例:
public class ReflectMethodConstructorDemo {
public static void main(String[] args)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<?>[] constructors1 = String.class.getDeclaredConstructors();
System.out.println("String getDeclaredConstructors 清單(數(shù)量 = " + constructors1.length + "):");
for (Constructor c : constructors1) {
System.out.println(c);
}
Constructor<?>[] constructors2 = String.class.getConstructors();
System.out.println("String getConstructors 清單(數(shù)量 = " + constructors2.length + "):");
for (Constructor c : constructors2) {
System.out.println(c);
}
System.out.println("====================");
Constructor constructor = String.class.getConstructor(String.class);
System.out.println(constructor);
String str = (String) constructor.newInstance("bbb");
System.out.println(str);
}
}
Array
數(shù)組在 Java 里是比較特殊的一種類型纠永,它可以賦值給一個(gè)對(duì)象引用鬓长。下面我們看一看利用反射創(chuàng)建數(shù)組的例子:
public class ReflectArrayDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls, 25);
//往數(shù)組里添加內(nèi)容
Array.set(array, 0, "Scala");
Array.set(array, 1, "Java");
Array.set(array, 2, "Groovy");
Array.set(array, 3, "Scala");
Array.set(array, 4, "Clojure");
//獲取某一項(xiàng)的內(nèi)容
System.out.println(Array.get(array, 3));
}
}
//Output:
//Scala
其中的 Array 類為 java.lang.reflect.Array
類。我們通過(guò) Array.newInstance
創(chuàng)建數(shù)組對(duì)象尝江,它的原型是:
public static Object newInstance(Class<?> componentType, int length)
throws NegativeArraySizeException {
return newArray(componentType, length);
}
動(dòng)態(tài)代理
動(dòng)態(tài)代理是反射的一個(gè)非常重要的應(yīng)用場(chǎng)景涉波。動(dòng)態(tài)代理常被用于一些 Java 框架中。例如 Spring 的 AOP 炭序,Dubbo 的 SPI 接口啤覆,就是基于 Java 動(dòng)態(tài)代理實(shí)現(xiàn)的。
靜態(tài)代理
靜態(tài)代理其實(shí)就是指設(shè)計(jì)模式中的代理模式惭聂。
代理模式為其他對(duì)象提供一種代理以控制對(duì)這個(gè)對(duì)象的訪問(wèn)窗声。
Subject 定義了 RealSubject 和 Proxy 的公共接口,這樣就在任何使用 RealSubject 的地方都可以使用 Proxy 辜纲。
abstract class Subject {
public abstract void Request();
}
RealSubject 定義 Proxy 所代表的真實(shí)實(shí)體笨觅。
class RealSubject extends Subject {
@Override
public void Request() {
System.out.println("真實(shí)的請(qǐng)求");
}
}
Proxy 保存一個(gè)引用使得代理可以訪問(wèn)實(shí)體,并提供一個(gè)與 Subject 的接口相同的接口侨歉,這樣代理就可以用來(lái)替代實(shí)體屋摇。
class Proxy extends Subject {
private RealSubject real;
@Override
public void Request() {
if (null == real) {
real = new RealSubject();
}
real.Request();
}
}
說(shuō)明:
靜態(tài)代理模式固然在訪問(wèn)無(wú)法訪問(wèn)的資源,增強(qiáng)現(xiàn)有的接口業(yè)務(wù)功能方面有很大的優(yōu)點(diǎn)幽邓,但是大量使用這種靜態(tài)代理,會(huì)使我們系統(tǒng)內(nèi)的類的規(guī)模增大火脉,并且不易維護(hù)牵舵;并且由于 Proxy 和 RealSubject 的功能本質(zhì)上是相同的柒啤,Proxy 只是起到了中介的作用,這種代理在系統(tǒng)中的存在畸颅,導(dǎo)致系統(tǒng)結(jié)構(gòu)比較臃腫和松散担巩。
動(dòng)態(tài)代理
為了解決靜態(tài)代理的問(wèn)題,就有了創(chuàng)建動(dòng)態(tài)代理的想法:
在運(yùn)行狀態(tài)中没炒,需要代理的地方涛癌,根據(jù) Subject 和 RealSubject,動(dòng)態(tài)地創(chuàng)建一個(gè) Proxy送火,用完之后拳话,就會(huì)銷毀,這樣就可以避免了 Proxy 角色的 class 在系統(tǒng)中冗雜的問(wèn)題了种吸。
Java 動(dòng)態(tài)代理基于經(jīng)典代理模式弃衍,引入了一個(gè) InvocationHandler,InvocationHandler 負(fù)責(zé)統(tǒng)一管理所有的方法調(diào)用坚俗。
動(dòng)態(tài)代理步驟:
- 獲取 RealSubject 上的所有接口列表镜盯;
- 確定要生成的代理類的類名,默認(rèn)為:
com.sun.proxy.$ProxyXXXX
猖败; - 根據(jù)需要實(shí)現(xiàn)的接口信息速缆,在代碼中動(dòng)態(tài)創(chuàng)建 該 Proxy 類的字節(jié)碼;
- 將對(duì)應(yīng)的字節(jié)碼轉(zhuǎn)換為對(duì)應(yīng)的 class 對(duì)象恩闻;
- 創(chuàng)建
InvocationHandler
實(shí)例 handler激涤,用來(lái)處理Proxy
所有方法調(diào)用; - Proxy 的 class 對(duì)象 以創(chuàng)建的 handler 對(duì)象為參數(shù)判呕,實(shí)例化一個(gè) proxy 對(duì)象倦踢。
從上面可以看出,JDK 動(dòng)態(tài)代理的實(shí)現(xiàn)是基于實(shí)現(xiàn)接口的方式侠草,使得 Proxy 和 RealSubject 具有相同的功能辱挥。
但其實(shí)還有一種思路:通過(guò)繼承。即:讓 Proxy 繼承 RealSubject边涕,這樣二者同樣具有相同的功能晤碘,Proxy 還可以通過(guò)重寫 RealSubject 中的方法,來(lái)實(shí)現(xiàn)多態(tài)功蜓。CGLIB 就是基于這種思路設(shè)計(jì)的园爷。
在 Java 的動(dòng)態(tài)代理機(jī)制中,有兩個(gè)重要的類(接口)式撼,一個(gè)是 InvocationHandler
接口童社、另一個(gè)則是 Proxy
類,這一個(gè)類和一個(gè)接口是實(shí)現(xiàn)我們動(dòng)態(tài)代理所必須用到的著隆。
InvocationHandler 接口
InvocationHandler
接口定義:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn) InvocationHandler
這個(gè)接口扰楼,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)到了一個(gè) Handler呀癣,當(dāng)我們通過(guò)代理對(duì)象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由 InvocationHandler
這個(gè)接口的 invoke
方法來(lái)進(jìn)行調(diào)用弦赖。
我們來(lái)看看 InvocationHandler 這個(gè)接口的唯一一個(gè)方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
參數(shù)說(shuō)明:
- proxy - 代理的真實(shí)對(duì)象项栏。
-
method - 所要調(diào)用真實(shí)對(duì)象的某個(gè)方法的
Method
對(duì)象 - args - 所要調(diào)用真實(shí)對(duì)象某個(gè)方法時(shí)接受的參數(shù)
如果不是很明白,等下通過(guò)一個(gè)實(shí)例會(huì)對(duì)這幾個(gè)參數(shù)進(jìn)行更深的講解蹬竖。
Proxy 類
Proxy
這個(gè)類的作用就是用來(lái)動(dòng)態(tài)創(chuàng)建一個(gè)代理對(duì)象的類膝捞,它提供了許多的方法撒穷,但是我們用的最多的就是 newProxyInstance
這個(gè)方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
這個(gè)方法的作用就是得到一個(gè)動(dòng)態(tài)的代理對(duì)象。
參數(shù)說(shuō)明:
- loader - 一個(gè) ClassLoader 對(duì)象,定義了由哪個(gè) ClassLoader 對(duì)象來(lái)對(duì)生成的代理對(duì)象進(jìn)行加載钢坦。
- interfaces - 一個(gè) Interface 對(duì)象的數(shù)組席覆,表示的是我將要給我需要代理的對(duì)象提供一組什么接口浙炼,如果我提供了一組接口給它粒梦,那么這個(gè)代理對(duì)象就宣稱實(shí)現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了
- h - 一個(gè) InvocationHandler 對(duì)象同辣,表示的是當(dāng)我這個(gè)動(dòng)態(tài)代理對(duì)象在調(diào)用方法的時(shí)候拷姿,會(huì)關(guān)聯(lián)到哪一個(gè) InvocationHandler 對(duì)象上
動(dòng)態(tài)代理實(shí)例
上面的內(nèi)容介紹完這兩個(gè)接口(類)以后,我們來(lái)通過(guò)一個(gè)實(shí)例來(lái)看看我們的動(dòng)態(tài)代理模式是什么樣的:
首先我們定義了一個(gè) Subject 類型的接口旱函,為其聲明了兩個(gè)方法:
public interface Subject {
void hello(String str);
String bye();
}
接著响巢,定義了一個(gè)類來(lái)實(shí)現(xiàn)這個(gè)接口,這個(gè)類就是我們的真實(shí)對(duì)象棒妨,RealSubject 類:
public class RealSubject implements Subject {
@Override
public void hello(String str) {
System.out.println("Hello " + str);
}
@Override
public String bye() {
System.out.println("Goodbye");
return "Over";
}
}
下一步踪古,我們就要定義一個(gè)動(dòng)態(tài)代理類了,前面說(shuō)個(gè)券腔,每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn) InvocationHandler 這個(gè)接口伏穆,因此我們這個(gè)動(dòng)態(tài)代理類也不例外:
public class InvocationHandlerDemo implements InvocationHandler {
// 這個(gè)就是我們要代理的真實(shí)對(duì)象
private Object subject;
// 構(gòu)造方法,給我們要代理的真實(shí)對(duì)象賦初值
public InvocationHandlerDemo(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable {
// 在代理真實(shí)對(duì)象前我們可以添加一些自己的操作
System.out.println("Before method");
System.out.println("Call Method: " + method);
// 當(dāng)代理對(duì)象調(diào)用真實(shí)對(duì)象的方法時(shí)纷纫,其會(huì)自動(dòng)的跳轉(zhuǎn)到代理對(duì)象關(guān)聯(lián)的handler對(duì)象的invoke方法來(lái)進(jìn)行調(diào)用
Object obj = method.invoke(subject, args);
// 在代理真實(shí)對(duì)象后我們也可以添加一些自己的操作
System.out.println("After method");
System.out.println();
return obj;
}
}
最后枕扫,來(lái)看看我們的 Client 類:
public class Client {
public static void main(String[] args) {
// 我們要代理的真實(shí)對(duì)象
Subject realSubject = new RealSubject();
// 我們要代理哪個(gè)真實(shí)對(duì)象,就將該對(duì)象傳進(jìn)去辱魁,最后是通過(guò)該真實(shí)對(duì)象來(lái)調(diào)用其方法的
InvocationHandler handler = new InvocationHandlerDemo(realSubject);
/*
* 通過(guò)Proxy的newProxyInstance方法來(lái)創(chuàng)建我們的代理對(duì)象烟瞧,我們來(lái)看看其三個(gè)參數(shù)
* 第一個(gè)參數(shù) handler.getClass().getClassLoader() ,我們這里使用handler這個(gè)類的ClassLoader對(duì)象來(lái)加載我們的代理對(duì)象
* 第二個(gè)參數(shù)realSubject.getClass().getInterfaces()染簇,我們這里為代理對(duì)象提供的接口是真實(shí)對(duì)象所實(shí)行的接口参滴,表示我要代理的是該真實(shí)對(duì)象,這樣我就能調(diào)用這組接口中的方法了
* 第三個(gè)參數(shù)handler锻弓, 我們這里將這個(gè)代理對(duì)象關(guān)聯(lián)到了上方的 InvocationHandler 這個(gè)對(duì)象上
*/
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.hello("World");
String result = subject.bye();
System.out.println("Result is: " + result);
}
}
我們先來(lái)看看控制臺(tái)的輸出:
com.sun.proxy.$Proxy0
Before method
Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
Hello World
After method
Before method
Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
Goodbye
After method
Result is: Over
我們首先來(lái)看看 com.sun.proxy.$Proxy0
這東西砾赔,我們看到,這個(gè)東西是由 System.out.println(subject.getClass().getName());
這條語(yǔ)句打印出來(lái)的,那么為什么我們返回的這個(gè)代理對(duì)象的類名是這樣的呢过蹂?
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
可能我以為返回的這個(gè)代理對(duì)象會(huì)是 Subject 類型的對(duì)象十绑,或者是 InvocationHandler 的對(duì)象聚至,結(jié)果卻不是酷勺,首先我們解釋一下為什么我們這里可以將其轉(zhuǎn)化為 Subject 類型的對(duì)象?
原因就是:在 newProxyInstance 這個(gè)方法的第二個(gè)參數(shù)上扳躬,我們給這個(gè)代理對(duì)象提供了一組什么接口脆诉,那么我這個(gè)代理對(duì)象就會(huì)實(shí)現(xiàn)了這組接口,這個(gè)時(shí)候我們當(dāng)然可以將這個(gè)代理對(duì)象強(qiáng)制類型轉(zhuǎn)化為這組接口中的任意一個(gè)贷币,因?yàn)檫@里的接口是 Subject 類型击胜,所以就可以將其轉(zhuǎn)化為 Subject 類型了。
同時(shí)我們一定要記住役纹,通過(guò) Proxy.newProxyInstance
創(chuàng)建的代理對(duì)象是在 jvm 運(yùn)行時(shí)動(dòng)態(tài)生成的一個(gè)對(duì)象偶摔,它并不是我們的 InvocationHandler 類型,也不是我們定義的那組接口的類型促脉,而是在運(yùn)行是動(dòng)態(tài)生成的一個(gè)對(duì)象辰斋,并且命名方式都是這樣的形式,以$開(kāi)頭瘸味,proxy 為中宫仗,最后一個(gè)數(shù)字表示對(duì)象的標(biāo)號(hào)。
接著我們來(lái)看看這兩句
subject.hello("World");
String result = subject.bye();
這里是通過(guò)代理對(duì)象來(lái)調(diào)用實(shí)現(xiàn)的那種接口中的方法旁仿,這個(gè)時(shí)候程序就會(huì)跳轉(zhuǎn)到由這個(gè)代理對(duì)象關(guān)聯(lián)到的 handler 中的 invoke 方法去執(zhí)行藕夫,而我們的這個(gè) handler 對(duì)象又接受了一個(gè) RealSubject 類型的參數(shù),表示我要代理的就是這個(gè)真實(shí)對(duì)象枯冈,所以此時(shí)就會(huì)調(diào)用 handler 中的 invoke 方法去執(zhí)行毅贮。
我們看到,在真正通過(guò)代理對(duì)象來(lái)調(diào)用真實(shí)對(duì)象的方法的時(shí)候尘奏,我們可以在該方法前后添加自己的一些操作滩褥,同時(shí)我們看到我們的這個(gè) method 對(duì)象是這樣的:
public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
正好就是我們的 Subject 接口中的兩個(gè)方法,這也就證明了當(dāng)我通過(guò)代理對(duì)象來(lái)調(diào)用方法的時(shí)候罪既,起實(shí)際就是委托由其關(guān)聯(lián)到的 handler 對(duì)象的 invoke 方法中來(lái)調(diào)用铸题,并不是自己來(lái)真實(shí)調(diào)用,而是通過(guò)代理的方式來(lái)調(diào)用的琢感。
小結(jié)
反射應(yīng)用
歡迎工作一到五年的Java程序員朋友們加入Java架構(gòu)交流:810589193
本群提供免費(fèi)的學(xué)習(xí)指導(dǎo) 架構(gòu)資料 以及免費(fèi)的解答
不懂得問(wèn)題都可以在本群提出來(lái) 之后還會(huì)有職業(yè)生涯規(guī)劃以及面試指導(dǎo)