談談Java反射:從入門到實踐,再到原理



前言

反射是Java底層框架的靈魂技術,學習反射非常有必要,本文將從入門概念,到實踐,再到原理講解反射,希望對大家有幫助。

反射理解

官方解析

Oracle 官方對反射的解釋是:

<code>Reflection is commonly used by programs which require the ability to examine or
modify the runtime behavior of applications running in the Java virtual machine.
This is a relatively advanced feature and should be used only by developers who
have a strong grasp of the fundamentals of the language. With that caveat in
mind, reflection is a powerful technique and can enable applications to perform
operations which would otherwise be impossible./<code>

Java 的反射機制是指在運行狀態中,對於任意一個類都能夠知道這個類所有的屬性和方法; 並且對於任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成為Java語言的反射機制。

白話理解

正射

萬物有陰必有陽,有正必有反。既然有反射,就必有“正射”。

那麼正射是什麼呢?

我們在編寫代碼時,當需要使用到某一個類的時候,都會先了解這個類是做什麼的。然後實例化這個類,接著用實例化好的對象進行操作,這就是正射。

<code>Student student = new Student();
student.doHomework("數學");
/<code>

反射

反射就是,一開始並不知道我們要初始化的類對象是什麼,自然也無法使用 new 關鍵字來創建對象了。

<code> Class clazz = Class.forName("reflection.Student");
Method method = clazz.getMethod("doHomework", String.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, "語文");
/<code>

正射與反射對比

以上兩段代碼,執行效果是一樣的,如圖

談談Java反射:從入門到實踐,再到原理

但是,其實現的過程還是有很大的差別的:

  • 第一段代碼在未運行前就已經知道了要運行的類是Student;
  • 第二段代碼則是到整個程序運行的時候,從字符串reflection.Student,才知道要操作的類是Student。

結論

反射就是在運行時才知道要操作的類是什麼,並且可以在運行時獲取類的完整構造,並調用對應的方法。

Class 對象理解

要理解Class對象,我們先來了解一下RTTI吧。 RTTI(Run-Time Type Identification)運行時類型識別,其作用是在運行時識別一個對象的類型和類的信息。

Java是如何讓我們在運行時識別對象和類的信息的?主要有兩種方式: 一種是傳統的RRTI,它假定我們在編譯期已知道了所有類型。 另一種是反射機制,它允許我們在運行時發現和使用類的信息。

每個類都有一個Class對象,每當編譯一個新類就產生一個Class對象(更恰當地說,是被保存在一個同名的.class文件中)。比如創建一個Student類,那麼,JVM就會創建一個Student對應Class類的Class對象,該Class對象保存了Student類相關的類型信息。

談談Java反射:從入門到實踐,再到原理

Class類的對象作用是運行時提供或獲得某個對象的類型信息

反射的基本使用

獲取 Class 類對象

獲取反射中的Class對象有三種方法。

第一種,使用 Class.forName 靜態方法。

<code>Class class1 = Class.forName("reflection.TestReflection");
/<code>

第二種,使用類的.class 方法

<code>Class class2 = TestReflection.class;
/<code>

第三種,使用實例對象的 getClass() 方法。

<code>TestReflection testReflection = new TestReflection();
Class class3 = testReflection.getClass();/<code>
談談Java反射:從入門到實踐,再到原理

反射創造對象,獲取方法,成員變量,構造器

本小節學習反射的基本API用法,如獲取方法,成員變量等。

反射創造對象

通過反射創建類對象主要有兩種方式:

談談Java反射:從入門到實踐,再到原理

實例代碼:

<code>//方式一
Class class1 = Class.forName("reflection.Student");
Student student = (Student) class1.newInstance();
System.out.println(student);

//方式二
Constructor constructor = class1.getConstructor();
Student student1 = (Student) constructor.newInstance();
System.out.println(student1);/<code>

運行結果:

談談Java反射:從入門到實踐,再到原理

反射獲取類的構造器

談談Java反射:從入門到實踐,再到原理

看一個例子吧:

<code>Class class1 = Class.forName("reflection.Student");
Constructor[] constructors = class1.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
System.out.println(constructors[i]);
}
/<code>
談談Java反射:從入門到實踐,再到原理

反射獲取類的成員變量

談談Java反射:從入門到實踐,再到原理

看demo:

<code>// student 一個私有屬性age,一個公有屬性email
public class Student {

private Integer age;

public String email;
}

public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class class1 = Class.forName("reflection.Student");
Field email = class1.getField("email");
System.out.println(email);
Field age = class1.getField("age");
System.out.println(age);
}
}
/<code>

運行結果:

談談Java反射:從入門到實踐,再到原理

即getField(String name) 根據參數變量名,返回一個具體的具有public屬性的成員變量,如果該變量不是public屬性,則報異常。

反射獲取類的方法

談談Java反射:從入門到實踐,再到原理

demo

<code>public class Student {

private void testPrivateMethod() {


}
public void testPublicMethod() {

}
}

public class TestReflection {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class class1 = Class.forName("reflection.Student");

Method[] methods = class1.getMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
}
}
/<code>

運行結果:

談談Java反射:從入門到實踐,再到原理

反射的實現原理

通過上一小節學習,我們已經知道反射的基本API用法了。接下來,跟著一個例子,學習反射方法的執行鏈路。

<code>public class TestReflection {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("reflection.TestReflection");
Method method = clazz.getMethod("target", String.class);
method.invoke(null, "666");
}

public static void target(String str) {
//打印堆棧信息
new Exception("#" +str).printStackTrace();
System.out.println("invoke target method");
}
}
/<code>

堆棧信息反映出反射調用鏈路:

<code>java.lang.Exception: #666
invoke target method
at reflection.TestReflection.target(TestReflection.java:17)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at reflection.TestReflection.main(TestReflection.java:11)/<code>

invoke方法執行時序圖

談談Java反射:從入門到實踐,再到原理

我們跟著反射鏈路去看一下源碼,先看Method的invoke方法:

<code>public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
//校驗權限
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor(); //獲取MethodAccessor
}
//返回MethodAccessor.invoke
return ma.invoke(obj, args);
}

/<code>

由上可知道,Method 的 invoke 方法,其實是返回接口MethodAccessor的invoke方法。MethodAccessor接口有三個實現類,到底調用的是哪個類的 invoke 方法呢?

談談Java反射:從入門到實踐,再到原理

進入acquireMethodAccessor方法,可以看到MethodAccessor由ReflectionFactory 的 newMethodAccessor方法決定。

談談Java反射:從入門到實踐,再到原理

再進ReflectionFactory的newMethodAccessor方法,我們可以看到返回的是DelegatingMethodAccessorImpl對象,也就是說調用的是它的invoke方法。

談談Java反射:從入門到實踐,再到原理

再看DelegatingMethodAccessorImpl的invoke方法

談談Java反射:從入門到實踐,再到原理

DelegatingMethodAccessorImpl的invoke方法返回的是MethodAccessorImpl的invoke方法,而MethodAccessorImpl的invoke方法,由它的子類NativeMethodAccessorImpl重寫,這時候返回的是本地方法invoke0,如下


談談Java反射:從入門到實踐,再到原理

因此,Method的invoke方法,是由本地方法invoke0決定的,再底層就是c++相關了,有興趣的朋友可以繼續往下研究。

反射的一些應用以及問題

反射應用

反射是Java框架的靈魂技術,很多框架都使用了反射技術,如spring,Mybatis,Hibernate等。

JDBC 的數據庫的連接

在JDBC連接數據庫中,一般包括加載驅動,獲得數據庫連接等步驟。而加載驅動,就是引入相關Jar包後,通過Class.forName() 即反射技術,加載數據庫的驅動程序。

Spring 框架的使用

Spring 通過 XML 配置模式裝載 Bean,也是反射的一個典型例子。

裝載過程:

  • 將程序內XML 配置文件加載入內存中
  • Java類解析xml裡面的內容,得到相關字節碼信息
  • 使用反射機制,得到Class實例
  • 動態配置實例的屬性,使用

這樣做當然是有好處的:

不用每次都去new實例了,並且可以修改配置文件,比較靈活。

反射存在的問題

性能問題

java反射的性能並不好,原因主要是編譯器沒法對反射相關的代碼做優化。

安全問題

我們知道單例模式的設計過程中,會強調將構造器設計為私有,因為這樣可以防止從外部構造對象。但是反射可以獲取類中的域、方法、構造器,修改訪問權限。所以這樣並不一定是安全的。

看個例子吧,通過反射使用私有構造器實例化。

<code>public class Student { 

private String name;
private Student(String name) {
System.out.println("我是私有構造器,我被實例化了");
this.name = name;
}
public void doHomework(String subject) {
System.out.println("我的名字是" + name);
System.out.println("我在做"+subject+"作業");
}
}
public class TestReflection {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("reflection.Student");
// 獲取私有構造方法對象
Constructor constructor = clazz.getDeclaredConstructor(String.class);
// true指示反射的對象在使用時應該取消Java語言訪問檢查。
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance("jay@huaxiao");
student.doHomework("數學");
}
}
/<code>

運行結果:

談談Java反射:從入門到實踐,再到原理

顯然,反射不管你是不是私有,一樣可以調用。 所以,使用反射通常需要程序的運行沒有安全限制。如果一個程序對安全性有強制要求,最好不要使用反射啦。

作者:Jay_huaxiao
原文鏈接:https://juejin.im/post/5de3242e6fb9a071886675d7


分享到:


相關文章: