JAVA反射呼叫詳解
JAVA反射呼叫的確是一種很神奇的機制,在專案中使用後戀戀不忘,現將其好好整理一下。
反射機制概述
反射機制是在執行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個物件,都能夠呼叫它的任意一個方法和屬性。
這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為JAVA語言的反射機制。
反射機制主要提供了以下功能:
- 在執行時判斷任意一個物件所屬的類;
- 在執行時構造任意一個類的物件;
- 在執行時判斷任意有一個類所具有的的成員變數和方法;
- 在執行時呼叫任意一個物件的方法;
- 生成動態代理
- 反射最重要的用途就是開發各種通用框架。大大提高了程式的擴充套件性 。很多框架(比如Spring、Tomcat)都是配置化的(比如通過XML檔案配置JavaBean,Action之類的),為了保證框架的通用性,它們可能需要根據配置檔案載入不同的物件或類,呼叫不同的方法,這個時候就必須用到反射——執行時動態載入需要載入的物件。
舉個例子:
Tomcat伺服器程式,提供了處理請求和應答的方式。因為具體的處理動作不同,所以對外提供了一個介面,由開發者來實現具體的請求和應答處理。該介面還有一個配置檔案可供修改。
interface Servlet //Tomcat提供的介面
class MySevlet implements Servlet{
//定義了具體的請求和應答處理方式
}
配置檔案: web.xml
<servlet-name>
MySevlet
</servlet-name>
Tomcat動態的獲取自定義的類,並呼叫其方法,這個過程就是反射呼叫。
從JAVA虛擬機器看反射
首先我們瞭解一下JVM,什麼是JVM,Java的虛擬機器,java之所以能跨平臺就是因為這個東西,你可以理解成一個程序,程式,只不過他的作用是用來跑你的程式碼的。上圖是java的記憶體模型,我們關注的點,一個方法區,一個棧,一個堆,初學的時候老師不深入的話只告訴你java的記憶體分為堆和棧,易懂點吧!
來看建立物件的過程:
Object o = new Object();
首先JVM會啟動,你的程式碼會編譯成一個.class檔案,然後被類載入器載入進jvm的記憶體中,你的類Object載入到方法區中,建立了Object類的class物件到堆中,注意這個不是new出來的物件,而是類的型別物件,每個類只有一個class物件,作為方法區類的資料結構的介面。jvm建立物件前,會先檢查類是否載入,尋找類對應的class物件,若載入好,則為你的物件分配記憶體,初始化也就是程式碼:new Object()。
上面的流程就是你自己寫好的程式碼扔給jvm去跑,跑完就over了,jvm關閉,你的程式也停止了。
為什麼要講這個呢?因為要理解反射必須知道它在什麼場景下使用。題主想想上面的程式物件是自己new的,程式相當於寫死了給jvm去跑。假如一個伺服器上突然遇到某個請求哦要用到某個類,哎呀但沒載入進jvm,是不是要停下來自己寫段程式碼,new一下,哦啟動一下伺服器,(腦殘)!
反射是什麼呢?當我們的程式在執行時,需要動態的載入一些類這些類可能之前用不到所以不用載入到jvm,舉個例子我們的專案底層有時是用mysql,有時用oracle,需要動態地根據實際情況載入驅動類,這個時候反射就有用了,假設 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection這兩個類我們要用,這時候我們的程式就寫得比較動態化,通過Class tc = Class.forName(“com.java.dbtest.TestConnection”);通過類的全類名讓jvm在伺服器中找到並載入這個類,而如果是oracle則傳入的引數就變成另一個了。這時候就可以看到反射的好處了,這個動態性就體現出java的特性了!當然這裡只是舉了反射的一個應用,實際還有其他作用,只是這個例子能更好地理解!
Class類
Java程式在執行時,Java執行時系統一直對所有的物件進行所謂的執行時型別標識。這項資訊紀錄了每個物件所屬的類。虛擬機器通常使用執行時型別資訊選準正確方法去執行,用來儲存這些型別資訊的類是Class類。Class類封裝一個物件和介面執行時的狀態,當裝載類時,Class型別的物件自動建立。
Class 沒有公共構造方法。Class 物件是在載入類時由 Java 虛擬機器以及通過呼叫類載入器中的 defineClass 方法自動構造的,因此不能顯式地宣告一個Class物件。
虛擬機器為每種型別管理一個獨一無二的Class物件。也就是說,每個類(型)都有一個Class物件。執行程式時,Java虛擬機器(JVM)首先檢查是否所要載入的類對應的Class物件是否已經載入。如果沒有載入,JVM就會根據類名查詢.class檔案,並將其Class物件載入。
基本的 Java 型別(boolean、byte、char、short、int、long、float 和 double)和關鍵字 void 也都對應一個 Class 物件。
每個陣列屬於被對映為Class物件的一個類,所有具有相同元素型別和維數的陣列都共享該 Class 物件。
一般某個類的Class物件被載入記憶體,它就用來建立這個類的所有物件。
/*
該類就可以獲取位元組碼檔案中的所有內容
反射就是要依靠該類來完成的
*/
class Class{
提供獲取位元組碼檔案中的內容。
比如:
名稱
欄位
建構函式
一般函式
}
獲得Class物件
在反射呼叫中,如果想要對位元組碼檔案進行解剖,必須要有位元組碼檔案物件。
三種方式獲取Class物件:
- Object類中的getClass方法。 這也是最常見的產生Class物件的方法。想要用這種方式,必須要明確具體的類,並建立物件,麻煩。
Person p = new Person();
Class clazz = p.getClass();
- 直接獲取某一個物件的class. 任何資料型別都具備一個靜態的屬性.class來獲取其對應的class物件。相對簡單,但還是要明確用到的類中的靜態成員。還是不夠擴充套件
Class clazz = Person.class;
- 使用Class類中的forName靜態方法 只要通過給定的類的字串名稱就可以獲取該類,更為擴充套件
.但是這裡的className**需要類的完全限定名**,因為它是字串,和導沒導包沒任何關係
Class clazz = Class.forName("com.example.hgx.Person");
//得新增異常ClassNotFoundException
反射的基本運用
反射方式建立例項
- 建立空參的例項。 使用Class物件的newInstance()方法來建立Class物件對應類的例項。
//原始:new的時候,先根據被new的類的名稱找尋該類的位元組碼檔案,並載入進記憶體,
//並建立該位元組碼檔案物件,並接著建立該位元組檔案對應的Person物件。
Person p = new Person();
//反射:找尋該類的名稱,並載入進記憶體,併產生Class物件
//在產生類的物件
Strint className = "com.example.hgx.Person";
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
2、建立帶引數的例項。 這時要通過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立例項。
class Person {
public String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
//獲取Person類帶一個(String,int)引數的構造器
Strint className = "com.example.hgx.Person";
Class clazz = Class.forName(className);
Constructor constructor = clazz.getConstructor(String.class,int.class);
Object obj = constructor.newInstance("小明",24);
反射方式獲取欄位(成員變數)
主要是這幾個方法
- getField: 訪問公有的成員變數
- getDeclaredField:所有已宣告的成員變數。但不能得到其父類的成員變數
Strint className = "com.example.hgx.Person";
Class clazz = Class.forName(className);
Object obj = clazz.newInstance();
//訪問public欄位
Field field = clazz.getField("name");
field.set(obj,"小明");
Object o = field.get(obj);
//訪問private欄位,要取消許可權檢查.暴力訪問
Field field1 =clazz.getDeclaredField("age");
field1.setAccessible(true);
field1.set(obj,24);
Object o1 = field1.get(obj);
反射方式獲取方法
獲取某個Class物件的方法集合,主要有以下幾個方法:
- getDeclaredMethods() 方法返回類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。
- getMethods() 方法返回某個類的所有公用(public)方法,包括其繼承類的公用方法。
- getMethod方法返回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件
public Method getMethod(String name, Class<?>... parameterTypes)
例子:
Class clazz = Class.forName("com.example.hgx.Person");
Method[] methods = clazz.getMethods();//獲得公有方法
for(Method method: methods){
System.out.println(method);
}
Method[] all_methods = clazz.getDeclaredMethods();//只獲取本類中的所有方法,包含私有
//呼叫方法
Method method1 = clazz.getMethod("show",null);//Person中的show()方法,空參
Object obj = clazz.newInstance();
method1.invoke(obj,null);
//呼叫帶參方法
Method method = clazz.getMethod("paramMethod",String.class,int.class);
Object obj1 = clazz.newInstance();
method.invoke(obj1,"小明",24);