一:什麼是反射?
- JAVA反射機制是在運行狀態中,對於任何一個類,都能夠知道這個類的所有屬性和方法。
- 對於任何一個對象,都能夠調用它的任意方法和屬性,這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。
二: Class 對象:類的信息,什麼是 RTTI?
- RTTI 即 Runtime Type Information,顧名思義,也就是在運行時,識別對象和類的信息。
- RTTI 有兩種,一種是“傳統的” RTTI,它假定我們在編譯時就已經知道了所有的類型;另一種是“反射”機制,它允許我們在運行時發現和使用類的信息。
理解1:
Class 對象包含了與類有關的信息,事實上 Java 中是用 Class 對象來創建這個類中的所有的對象的。每當編譯完成,就會生成一個 Class 對象,被保存在 .class 文件中(每個類都會產生一個對應的Class對象)。JVM 使用 ClassLoader 來加載對象,所有的類都是在對其第一次使用(靜態成員被引用,靜態常量除外)或者用 new 關鍵字創建對象後,動態加載到 JVM 中的。所謂的動態加載也就是在被使用到時纔去加載。
理解2:
理解RTTI在Java中的工作原理,首先需要知道類型信息在運行時是如何表示的,這是由Class對象來完成的,它包含了與類有關的信息。Class對象就是用來創建所有“常規”對象的,Java使用Class對象來執行RTTI,即使你正在執行的是類似類型轉換這樣的操作。每個類都會產生一個對應的Class對象,也就是保存在.class文件。所有類都是在對其第一次使用時,動態加載到JVM的,當程序創建一個對類的靜態成員的引用時,就會加載這個類。Class對象僅在需要的時候纔會加載,static初始化是在類加載時進行的。
理解3:
類加載器首先會檢查這個類的Class對象是否已被加載過,如果尚未加載,默認的類加載器就會根據類名查找對應的.class文件。想在運行時使用類型信息,必須獲取對象(比如類Base對象)的Class對象的引用,使用功能Class.forName(“Base”)可以實現該目的,或者使用base.class。注意,有一點很有趣,使用功能”.class”來創建Class對象的引用時,不會自動初始化該Class對象,使用forName()會自動初始化該Class對象。
三:使用反射的前提
要先獲取到該類的字節碼文件對象(.class),通過字節碼文件對象,就能夠通過該類中的方法獲取到我們想要的所有信息(方法,屬性,類名,父類名,實現的所有接口等等),每一個類對應着一個字節碼文件也就對應着一個Class類型的對象,也就是字節碼文件對象。
獲取字節碼文件對象(獲取class對象的方式)的三種方式:
- 根據類名:類名.class
- 根據對象:對象.getClass()
- 根據全限定類名:Class.forName(全限定類名)
代碼示例如下,三個對象一致,1個類在 JVM 中只會有一個 Class 對象實例
public static void main(String[] args) throws ClassNotFoundException {
//對象.getClass()
Customer customer=new Customer();
Class clzz=customer.getClass();
System.out.println("clzz = " + clzz);
//類名.class
Class clzz2=Customer.class;
System.out.println("clzz2 = " + clzz2);
//Class.forName(全限定類名)
Class clzz3=Class.forName("com.java.reflect.Customer");
System.out.println("clzz3 = " + clzz3);
//jvm中只有一個對象實例
System.out.println(clzz==clzz2);
}
四:Java中主要由以下的類來實現Java反射機制(這些類都位於java.lang.reflect包中),
- Class類:Class類是Reflection API中的核心類。
- Field類:代表類的成員變量(成員變量也稱爲類的屬性)。
- Method類:代表類的方法。
- Constructor類:代表類的構造方法。
- Array類:提供了動態創建數組,以及訪問數組的元素的靜態方法。
五:Class類的主要方法如下,
- getName():獲得類的完整名字。 getFields():獲得類的public類型的屬性。
- getDeclaredFields():獲得類的所有屬性。
- getMethods():獲得類的public類型的方法。
- getDeclaredMethods():獲得類的所有方法。
- getMethod(String name, Class[] parameterTypes):獲得類的特定方法,name參數指定方法的名字,parameterTypes參數指定方法的參數類型。
- getConstrutors():獲得類的public類型的構造方法。
- getConstrutor(Class[] parameterTypes):獲得類的特定構造方法,parameterTypes參數指定構造方法的參數類型。
- newInstance():通過類的不帶參數的構造方法創建這個類的一個對象。
六:舉例,
6-1:通過有參構造,創建對象,總結如下,源碼參考:利用有參構造創建類對象
- 不用反射:創建對象,需要手動new對象不能動態創建,new的時候根據new的類名尋找字節碼文件,加載進入內存,創建Class對象,並接着創建對應的 Teacher 對象
- 通過反射:只需要一個名字,就可以創建對象。尋找該名稱的類文件,加載進內存,並創建Class對象,調用有參構造方法初始化變量,通過Class對象的newInstance()創建類對象。
public static void main(String[] args) throws Exception {
//不用反射: 創建對象,需要手動new對象不能動態創建
//new的時候根據new的類名尋找字節碼文件,加載進入內存,創建Class對象,並接着創建對應的 Teacher 對象
Object obj1= new Teacher("張三", 22);
System.out.println("不用反射: " + obj1);
System.out.println("*********************************************************************");
//Class.forName(全限定類名)
//通過反射:只需要一個名字,就可以創建對象。尋找該名稱的類文件,加載進內存,並創建Class對象
Class clzz3 = Class.forName("com.java.reflect.Teacher");
//調用有參構造方法初始化變量
Constructor constructor = clzz3.getConstructor(String.class, int.class);
////通過Class對象的newInstance()創建類對象。
Object obj2 = constructor.newInstance("張三", 22);
System.out.println("通過反射: " + obj2);
}
6-2:帶參數的方法,源碼參考:帶參數的方法反射調用
public static void main(String[] args) throws Exception {
//獲取字節碼文件
Class classs = Class.forName("com.java.reflect.Student");
//獲取其實例對象
Student stu = (Student) classs.newInstance();
//獲取帶參數的方法,爲方法名
Method methodSet = classs.getDeclaredMethod("setName", String.class);
Method methodGet = classs.getDeclaredMethod("getName");
//設置可見性
methodSet.setAccessible(true);
//調用方法
methodSet.invoke(stu, "李四");
System.out.println(methodGet.invoke(stu));
}
七:動態代理
理解1:
代理:就是將實際對象的方法調用分離開來,從而允許我們對一些操作進行修改,或者添加額外的操作。而動態代理比代理的思想更進一步,它允許我們動態地創建代理並動態地處理對所代理方法的調用。
使用動態代理其實很簡單,首先要定義一個自己的 InvocationHandler
,然後再通過 Proxy.newProxyInstance
創建一個代理對象。
理解2:
代理模式是爲了提供額外或不同的操作,而插入的用來替代”實際”對象的對象,這些操作涉及到與”實際”對象的通信,因此代理通常充當中間人角色。Java的動態代理比代理的思想更前進了一步,它可以動態地創建並代理並動態地處理對所代理方法的調用。在動態代理上所做的所有調用都會被重定向到單一的調用處理器上,它的工作是揭示調用的類型並確定相應的策略。
通過調用Proxy靜態方法Proxy.newProxyInstance()可以創建動態代理,這個方法需要得到一個類加載器,一個你希望該代理實現的接口列表(不是類或抽象類),以及InvocationHandler的一個實現類。動態代理可以將所有調用重定向到調用處理器,因此通常會調用處理器的構造器傳遞一個”實際”對象的引用,從而將調用處理器在執行中介任務時,將請求轉發。
動態代理的概述及實現:
1、動態代理概述
動態代理:利用Java的反射技術(Java Reflection),在運行時創建一個實現某些給定接口的新類(也稱“動態代理類”)及其實例(對象);
代理的是接口(Interfaces),不是類(Class),更不是抽象類。
2、動態代理的實現
分三步,但是注意JDK提供的代理只能針對接口做代理,也就是下面的第二步返回的必須要是一個接口。
2.1 new出代理對象,通過實現InvacationHandler接口,然後new出代理對象來。
2.2 通過Proxy類中的靜態方法newProxyInstance,來將代理對象假裝成那個被代理的對象,也就是如果叫人幫我們代買火車票一樣,那個代理就假裝成我們自己本人。
2.3 執行方法,代理成功
代碼參考:
接口,
public interface IDo {
void doSomething();
void somethingElse(String arg);
}
實現類,
public class IDoImpl implements IDo {
public void doSomething() {
System.out.println("doSomething.");
}
public void somethingElse(String arg) {
System.out.println("somethingElse " + arg);
}
}
構建一個handler類來實現InvocationHandler接口,
public class DynamicProxyHandler implements InvocationHandler {
private Object proxyed;
public DynamicProxyHandler(Object proxyed) {
this.proxyed = proxyed;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
System.out.println("代理工作了.");
return method.invoke(proxyed, args);
}
public static void main(String[] args) {
IDoImpl real = new IDoImpl();
IDo proxy = (IDo) Proxy.newProxyInstance(
IDo.class.getClassLoader(), new Class[]{IDo.class},
new DynamicProxyHandler(real));
proxy.doSomething();
proxy.somethingElse("趙柳");
}
}
注意newProxyInstance的三個參數,第一個,類加載器,第二個被代理對象的接口,第三個代理對象。
使用動態代理的優勢:
- 更強的靈活性。我們不用在設計實現的時候就指定某一個代理類來代理某一個被代理對象,而是可以把這種指定延遲到程序運行時由 JVM 來實現
- 動態代理更爲統一與簡潔。
動態代理的應用:
Spring的AOP,加事務,加權限,加日誌。
八:反射總結
反射是無孔不入的,無論是私有方法還是私有內部類的方法,哪怕是匿名類的方法,也無法逃脫反射的調用。對於私有域來說也一樣,只有 final 域,纔不會被修改。反射可以說是給我們的程序留了一道後門,但是總的來說,從反射給我們帶來的優劣對比上看,利大於弊。
參考: