歡迎光臨
每天分享高質量文章

深入解析Java反射(1) – 基礎

點選上方“芋道原始碼”,選擇“置頂公眾號”

技術文章第一時間送達!

原始碼精品專欄

 

>  https://www.sczyh30.com/posts/Java/java-reflection-1/ ? 排版有點點崩嘿

因為本人最近正籌備Samsara框架的開發,而其中的IOC部分非常依靠反射,因此趁這個機會來總結一下關於Java反射的一些知識。本篇為基本篇,基於JDK 1.8。


一、回顧:什麼是反射?

反射(Reflection)是Java 程式開發語言的特徵之一,它允許執行中的 Java 程式獲取自身的資訊,並且可以操作類或物件的內部屬性。
Oracle官方對反射的解釋是

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions.
The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.

 簡而言之,透過反射,我們可以在執行時獲得程式或程式集中每一個型別的成員和成員的資訊。
程式中一般的物件的型別都是在編譯期就確定下來的,而Java反射機制可以動態地建立物件並呼叫其屬性,這樣的物件的型別在編譯期是未知的。所以我們可以透過反射機制直接建立物件,即使這個物件的型別在編譯期是未知的。
 反射的核心是JVM在執行時才動態載入類或呼叫方法/訪問屬性,它不需要事先(寫程式碼的時候或編譯期)知道執行物件是誰。

Java反射框架主要提供以下功能:

  • 1.在執行時判斷任意一個物件所屬的類;

  • 2.在執行時構造任意一個類的物件;

  • 3.在執行時判斷任意一個類所具有的成員變數和方法(透過反射甚至可以呼叫private方法);

  • 4.在執行時呼叫任意一個物件的方法

    重點:是執行時而不是編譯時

二、反射的主要用途

 很多人都認為反射在實際的Java開發應用中並不廣泛,其實不然。
 當我們在使用IDE(如Eclipse,IDEA)時,當我們輸入一個物件或類並想呼叫它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裡就會用到反射。
反射最重要的用途就是開發各種通用框架。
 很多框架(比如Spring)都是配置化的(比如透過XML檔案配置JavaBean,Action之類的),為了保證框架的通用性,它們可能需要根據配置檔案載入不同的物件或類,呼叫不同的方法,這個時候就必須用到反射——執行時動態載入需要載入的物件。
 舉一個例子,在運用Struts 2框架的開發中我們一般會在struts.xml裡去配置Action,比如:

1

2

3

4

5

6

<action name="login"

class="org.ScZyhSoft.test.action.SimpleLoginAction"

method="execute">

<result>/shop/shop-index.jspresult>

<result name=“error”>login.jspresult>

action>

配置檔案與Action建立了一種對映關係,當View層發出請求時,請求會被StrutsPrepareAndExecuteFilter攔截,然後StrutsPrepareAndExecuteFilter會去動態地建立Action實體。
——比如我們請求login.action,那麼StrutsPrepareAndExecuteFilter就會去解析struts.xml檔案,檢索action中name為login的Action,並根據class屬性建立SimpleLoginAction實體,並用invoke方法來呼叫execute方法,這個過程離不開反射。
對與框架開發人員來說,反射雖小但作用非常大,它是各種容器實現的核心。而對於一般的開發者來說,不深入框架開發則用反射用的就會少一點,不過瞭解一下框架的底層機制有助於豐富自己的程式設計思想,也是很有益的。

三、反射的基本運用

上面我們提到了反射可以用於判斷任意物件所屬的類,獲得Class物件,構造任意一個物件以及呼叫一個物件。這裡我們介紹一下基本反射功能的實現(反射相關的類一般都在java.lang.relfect包裡)。

1、獲得Class物件

方法有三種
(1)使用Class類的forName靜態方法:

1

2

3

4

5

public static Class> forName(String className)

```

在JDBC開發中常用此方法載入資料庫驅動:

```java

Class.forName(driver);

(2)直接獲取某一個物件的class,比如:

1

2

Class> klass = int.class;

Class> classInt = Integer.TYPE;

(3)呼叫某個物件的getClass()方法,比如:

1

2

StringBuilder str = new StringBuilder("123");

Class> klass = str.getClass();

2、判斷是否為某個類的實體

一般地,我們用instanceof關鍵字來判斷是否為某個類的實體。同時我們也可以藉助反射中Class物件的isInstance()方法來判斷是否為某個類的實體,它是一個Native方法:

1

public native boolean isInstance(Object obj);

3、建立實體

透過反射來生成物件主要有兩種方式。
(1)使用Class物件的newInstance()方法來建立Class物件對應類的實體。

1

2

Class> c = String.class;

Object str = c.newInstance();

(2)先透過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立實體。這種方法可以用指定的建構式構造類的實體。

1

2

3

4

5

6

7

//獲取String所對應的Class物件

Class> c = String.class;

//獲取String類帶一個String引數的建構式

Constructor constructor = c.getConstructor(String.class);

//根據建構式建立實體

Object obj = constructor.newInstance("23333");

System.out.println(obj);

4、獲取方法

獲取某個Class物件的方法集合,主要有以下幾個方法:
getDeclaredMethods()方法傳回類或介面宣告的所有方法,包括公共、保護、預設(包)訪問和私有方法,但不包括繼承的方法。

1

public Method[] getDeclaredMethods() throws SecurityException

getMethods()方法傳回某個類的所有公用(public)方法,包括其繼承類的公用方法。

1

public Method[] getMethods() throws SecurityException

getMethod方法傳回一個特定的方法,其中第一個引數為方法名稱,後面的引數為方法的引數對應Class的物件

1

public Method getMethod(String name, Class>... parameterTypes)

只是這樣描述的話可能難以理解,我們用例子來理解這三個方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

package org.ScZyhSoft.common;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

public class test1 {

public static void test() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

Class> c = methodClass.class;

Object object = c.newInstance();

Method[] methods = c.getMethods();

Method[] declaredMethods = c.getDeclaredMethods();

//獲取methodClass類的add方法

Method method = c.getMethod("add", int.class, int.class);

//getMethods()方法獲取的所有方法

System.out.println("getMethods獲取的方法:");

for(Method m:methods)

System.out.println(m);

//getDeclaredMethods()方法獲取的所有方法

System.out.println("getDeclaredMethods獲取的方法:");

for(Method m:declaredMethods)

System.out.println(m);

}

}

class methodClass {

public final int fuck = 3;

public int add(int a,int b) {

return a+b;

}

public int sub(int a,int b) {

return a+b;

}

}

程式執行的結果如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

getMethods獲取的方法:

public int org.ScZyhSoft.common.methodClass.add(int,int)

public int org.ScZyhSoft.common.methodClass.sub(int,int)

public final void java.lang.Object.wait() throws java.lang.InterruptedException

public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException

public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException

public boolean java.lang.Object.equals(java.lang.Object)

public java.lang.String java.lang.Object.toString()

public native int java.lang.Object.hashCode()

public final native java.lang.Class java.lang.Object.getClass()

public final native void java.lang.Object.notify()

public final native void java.lang.Object.notifyAll()

getDeclaredMethods獲取的方法:

public int org.ScZyhSoft.common.methodClass.add(int,int)

public int org.ScZyhSoft.common.methodClass.sub(int,int)

可以看到,透過getMethods()獲取的方法可以獲取到父類的方法,比如java.lang.Object下定義的各個方法。

5、獲取建構式資訊

獲取類建構式的用法與上述獲取方法的用法類似。主要是透過Class類的getConstructor方法得到Constructor類的一個實體,而Constructor類有一個newInstance方法可以建立一個物件實體:

1

public T newInstance(Object ... initargs)

此方法可以根據傳入的引數來呼叫對應的Constructor建立物件實體~

6、獲取類的成員變數(欄位)資訊

主要是這幾個方法,在此不再贅述:
getFiled: 訪問公有的成員變數
getDeclaredField:所有已宣告的成員變數。但不能得到其父類的成員變數
getFileds和getDeclaredFields用法同上(參照Method)

7、呼叫方法

當我們從類中獲取了一個方法後,我們就可以用invoke()方法來呼叫這個方法。invoke方法的原型為:

1

2

3

public Object invoke(Object obj, Object... args)

throws IllegalAccessException, IllegalArgumentException,

InvocationTargetException

下麵是一個實體:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

public class test1 {

public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {

Class> klass = methodClass.class;

//建立methodClass的實體

Object obj = klass.newInstance();

//獲取methodClass類的add方法

Method method = klass.getMethod("add",int.class,int.class);

//呼叫method對應的方法 => add(1,4)

Object result = method.invoke(obj,1,4);

System.out.println(result);

}

}

class methodClass {

public final int fuck = 3;

public int add(int a,int b) {

return a+b;

}

public int sub(int a,int b) {

return a+b;

}

}

關於invoke()方法的詳解,後面我會專門寫一篇文章來深入解析invoke的過程。

8、利用反射建立陣列

陣列在Java裡是比較特殊的一種型別,它可以賦值給一個Object Reference。下麵我們看一看利用反射建立陣列的例子:

1

2

3

4

5

6

7

8

9

10

11

12

public static void testArray() throws ClassNotFoundException {

Class> cls = Class.forName("java.lang.String");

Object array = Array.newInstance(cls,25);

//往陣列裡新增內容

Array.set(array,0,"hello");

Array.set(array,1,"Java");

Array.set(array,2,"fuck");

Array.set(array,3,"Scala");

Array.set(array,4,"Clojure");

//獲取某一項的內容

System.out.println(Array.get(array,3));

}

其中的Array類為java.lang.reflect.Array類。我們透過Array.newInstance()建立陣列物件,它的原型是:

1

2

3

4

public static Object newInstance(Class> componentType, int length)

throws NegativeArraySizeException {

return newArray(componentType, length);

}

而newArray()方法是一個Native方法,它在Hotspot JVM裡的具體實現我們後邊再研究,這裡先把原始碼貼出來

1

2

private static native Object newArray(Class> componentType, int length)

throws NegativeArraySizeException;

原始碼目錄:openjdk\hotspot\src\share\vm\runtime\reflection.cpp

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {

if (element_mirror == NULL) {

THROW_0(vmSymbols::java_lang_NullPointerException());

}

if (length < 0) {

THROW_0(vmSymbols::java_lang_NegativeArraySizeException());

}

if (java_lang_Class::is_primitive(element_mirror)) {

Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);

return TypeArrayKlass::cast(tak)->allocate(length, THREAD);

} else {

Klass* k = java_lang_Class::as_Klass(element_mirror);

if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {

THROW_0(vmSymbols::java_lang_IllegalArgumentException());

}

return oopFactory::new_objArray(k, length, THREAD);

}

}

另外,Array類的set()和get()方法都為Native方法,在HotSpot JVM裡分別對應Reflection::array_set和Reflection::array_get方法,這裡就不詳細解析了。

四、反射的一些註意事項(待補充)

由於反射會額外消耗一定的系統資源,因此如果不需要動態地建立一個物件,那麼就不需要用反射。
另外,反射呼叫方法時可以忽略許可權檢查,因此可能會破壞封裝性而導致安全問題




如果你對 Dubbo 感興趣,歡迎加入我的知識星球一起交流。

知識星球

目前在知識星球(https://t.zsxq.com/2VbiaEu)更新瞭如下 Dubbo 原始碼解析如下:

01. 除錯環境搭建
02. 專案結構一覽
03. 配置 Configuration
04. 核心流程一覽

05. 拓展機制 SPI

06. 執行緒池

07. 服務暴露 Export

08. 服務取用 Refer

09. 註冊中心 Registry

10. 動態編譯 Compile

11. 動態代理 Proxy

12. 服務呼叫 Invoke

13. 呼叫特性 

14. 過濾器 Filter

15. NIO 伺服器

16. P2P 伺服器

17. HTTP 伺服器

18. 序列化 Serialization

19. 叢集容錯 Cluster

20. 優雅停機

21. 日誌適配

22. 狀態檢查

23. 監控中心 Monitor

24. 管理中心 Admin

25. 運維命令 QOS

26. 鏈路追蹤 Tracing


一共 60 篇++

原始碼不易↓↓↓

點贊支援老艿艿↓↓

贊(0)

分享創造快樂