Java最強大的技術之一:反射

語言: CN / TW / HK

theme: condensed-night-purple

攜手創作,共同成長!這是我參與「掘金日新計劃 · 8 月更文挑戰」的第8天,點選檢視活動詳情

何為反射?

Java的 反射機制 是在執行狀態中,對於任意一個類,都能夠 知道這個類的所有屬性和方法 ;對於任意一個物件,都能夠 呼叫它的任意一個方法和屬性 ;這種 動態獲取的資訊以及動態呼叫物件的方法的功能 稱為 Java 語言的反射機制

簡而言之,只要你給我一個 .class ——類的名字,我就能通過反射獲取到類的屬性和方法。

反射是很多高階技術的基礎,Java 中的註解、動態代理,各種框架注入 Spring 、 MyBatis 等都用到了反射技術。

Class 類

既然反射能夠通過類的名字獲取到類的關鍵資訊,那麼我們就來聊聊 Class 類。

Class 類在 JDK 中的定義

java public final class Class<T> extends Object implements Serializable, GenericDeclaration, Type, AnnotatedElement

類 Class 的例項表示執行中的 Java 應用程式中的類和介面。

列舉 是一種類,註釋 是一種介面。

每個 陣列 也屬於一個類,這個類反映為一個 類物件 ,由具有相同元素型別和維數的所有陣列共享。

原始Java型別(布林型、位元組型、char型、short型、int型、long型、float型和double型)和關鍵字void也被表示為類物件。

類的載入過程

要想解剖一個類,必須先要獲取到該類的 位元組碼 檔案物件。

而解剖使用的就是 Class 類中的方法。所以先要獲取到每一個位元組碼檔案對應的Class型別的物件。

類的載入

那麼一個class檔案(.java檔案編譯後)在我們的硬碟上,是怎麼被載入到記憶體中的呢?

class進入記憶體分三步走:

1. Loading

Loading 就是把一個class檔案裝到記憶體中,它本來是class檔案上一個個的二進位制,一個個位元組,通過Loading把它放到記憶體。

2. Linking

Linking 的過程又分為:

  • verification:校驗裝到記憶體的class檔案是否符合class檔案的標準,加入裝進來的檔案不是“CA FE BA BE”這樣的,不符合class檔案標準,直接被拒。

class二進位制檔案

  • preparation:把class檔案靜態變數賦預設值,不是賦初始值。比如 static int i = 8 ,在該步驟只是把i賦了預設值0。

  • resolution:把class檔案常量池裡面用到的符號引用,把它轉成直接記憶體地址,可以訪問到的內容。

3. Initializing

Initializing 這一步就是將靜態變數賦初始值,比如上面的 static int i = 8 ,在這一步才賦初始值8。

載入過程

獲取 Class 類的三種方式

獲取 Class 類通常有以下三種方式:

  1. 物件.getClass()

通過物件的getClass()方法獲取Class類,說明物件已經建立好了,其實已經有Class類了。

  1. 類.class

這種方式獲取Class類,需要提前知道類的名稱,也就是專案中已經匯入了相應的包,依賴性強。

  1. Class.forName()

只需要傳入一個類的完全限定名即可。

推薦使用 Class.forName() 的方式獲取Class類。

反射常用到的API

  • 獲取類的構造方法:

```java public Constructor getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

public Constructor<?>[] getConstructors() throws SecurityException ```

  • 獲取類的成員變數

```java public Field getField(String name) throws NoSuchFieldException, SecurityException

public Field[] getFields() throws SecurityException ```

  • 獲取類的方法

```java public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException

public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException ```

For example

定義一個實體類:

```java public class User { private Integer id; private String userName; private String phoneNumber;

public User() {
}

public User(Integer id, String userName, String phoneNumber) {
    this.id = id;
    this.userName = userName;
    this.phoneNumber = phoneNumber;
}

public void myDefine() {
    System.out.println("xxx");
}

} ```

通過 反射 獲取 User 類的相關資訊:

java public class CreateObjectTest { public static void main(String[] args) throws ClassNotFoundException { //物件.getClass() // User user = new User(); // Class clazz = user.getClass(); // System.out.println(clazz.getPackage()); // System.out.println(clazz.getName()); // System.out.println(clazz.getCanonicalName()); // System.out.println(clazz.getSimpleName()); //類.class // Class clazz = User.class; //Class.forName Class clazz = Class.forName("com.xblzer.tryout.bean.User"); System.out.println("getConstructors:"); Arrays.stream(clazz.getConstructors()).iterator().forEachRemaining(System.out::println); System.out.println("getDeclaredFields:"); Arrays.stream(clazz.getDeclaredFields()).iterator().forEachRemaining(System.out::println); System.out.println("getDeclaredMethods:"); Arrays.stream(clazz.getDeclaredMethods()).iterator().forEachRemaining(System.out::println); } }

執行結果:

反射在 Spring 中的應用舉例

反射在眾多框架中都有普遍的應用。比如 Spring IOC 容器幫我們例項化眾多的bean,下面我們簡單模擬一下 反射 在其中起到的作用。

此處使用的案例接這篇:【設計模式】代理模式那些事兒:靜態代理,動態代理,JDK的動態代理,cglib,Spring AOP

Spring 配置檔案:

xml <bean id="pony" class="com.xblzer.dp.proxy.springaop.Pony"></bean>

使用的時候直接這樣就能拿到定義的類了:

java ApplicationContext ctx = new ClassPathXmlApplicationContext("app_aop.xml"); Pony pony = (Pony) ctx.getBean("pony");

那麼是怎麼做到的呢?就是通過 反射

Spring 通過配置檔案例項化物件,並將其放到容器的過程大概就是(模擬):

java //虛擬碼 //1.解析<bean .../>元素的id屬性得到該字串值為“pony” String idStr = "pony"; //解析<bean .../>元素的class屬性得到該字串值為“com.xblzer.dp.proxy.springaop.Pony” String classStr = "com.xblzer.dp.proxy.springaop.Pony"; //利用反射機制,通過classStr獲取Class類物件 Class<?> cls = Class.forName(classStr); //例項化物件 Object obj = cls.newInstance(); //放到Spring容器 Map<String, Object> container = new HashMap<>(); container.put(idStr, obj);

小結

多看一下 Class 類的API,諸多框架都用到了反射機制,而反射離不開呼叫這些基本的API。

JavaSE 8 API官網:http://docs.oracle.com/javase/8/docs/api/index.html

點個贊再走吧~