1. 程式人生 > >JAVA反射呼叫詳解

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虛擬機器看反射

image

首先我們瞭解一下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物件:

  1. Object類中的getClass方法。 這也是最常見的產生Class物件的方法。想要用這種方式,必須要明確具體的類,並建立物件,麻煩。
Person p = new Person();
Class clazz = p.getClass();
  1. 直接獲取某一個物件的class. 任何資料型別都具備一個靜態的屬性.class來獲取其對應的class物件。相對簡單,但還是要明確用到的類中的靜態成員。還是不夠擴充套件
Class clazz = Person.class;
  1. 使用Class類中的forName靜態方法 只要通過給定的類的字串名稱就可以獲取該類,更為擴充套件
    .但是這裡的className**需要類的完全限定名**,因為它是字串,和導沒導包沒任何關係
Class clazz = Class.forName("com.example.hgx.Person");
//得新增異常ClassNotFoundException

反射的基本運用

反射方式建立例項

  1. 建立空參的例項。 使用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);