談?wù)凧ava反射:從入門到實踐,再到原理
前言
反射是Java底層框架的靈魂技術(shù),學(xué)習(xí)反射非常有必要,本文將從入門概念,到實踐,再到原理講解反射,希望對大家有幫助。
反射理解
官方解析
Oracle 官方對反射的解釋是:
Reflection is commonly used by programs which require the ability to examine ormodify the runtime behavior of applications running in the Java virtual machine.This is a relatively advanced feature and should be used only by developers whohave a strong grasp of the fundamentals of the language. With that caveat inmind, reflection is a powerful technique and can enable applications to performoperations which would otherwise be impossible.
Java 的反射機制是指在運行狀態(tài)中,對于任意一個類都能夠知道這個類所有的屬性和方法;并且對于任意一個對象,都能夠調(diào)用它的任意一個方法;這種動態(tài)獲取信息以及動態(tài)調(diào)用對象方法的功能成為Java語言的反射機制。
白話理解
正射
萬物有陰必有陽,有正必有反。既然有反射,就必有“正射”。
那么正射是什么呢?
我們在編寫代碼時,當(dāng)需要使用到某一個類的時候,都會先了解這個類是做什么的。然后實例化這個類,接著用實例化好的對象進(jìn)行操作,這就是正射。
Student student = new Student();student.doHomework("數(shù)學(xué)");
反射
反射就是,一開始并不知道我們要初始化的類對象是什么,自然也無法使用 new 關(guān)鍵字來創(chuàng)建對象了。
Class clazz = Class.forName("reflection.Student");Method method = clazz.getMethod("doHomework", String.class);Constructor constructor = clazz.getConstructor();Object object = constructor.newInstance();method.invoke(object, "語文");
正射與反射對比
以上兩段代碼,執(zhí)行效果是一樣的,如圖

但是,其實現(xiàn)的過程還是有很大的差別的:
第一段代碼在未運行前就已經(jīng)知道了要運行的類是
Student;第二段代碼則是到整個程序運行的時候,從字符串
reflection.Student,才知道要操作的類是Student。
結(jié)論
反射就是在運行時才知道要操作的類是什么,并且可以在運行時獲取類的完整構(gòu)造,并調(diào)用對應(yīng)的方法。
Class 對象理解
要理解Class對象,我們先來了解一下RTTI吧。RTTI(Run-Time Type Identification)運行時類型識別,其作用是在運行時識別一個對象的類型和類的信息。
Java是如何讓我們在運行時識別對象和類的信息的?主要有兩種方式:一種是傳統(tǒng)的RRTI,它假定我們在編譯期已知道了所有類型。另一種是反射機制,它允許我們在運行時發(fā)現(xiàn)和使用類的信息。
每個類都有一個Class對象,每當(dāng)編譯一個新類就產(chǎn)生一個Class對象(更恰當(dāng)?shù)卣f,是被保存在一個同名的.class文件中)。比如創(chuàng)建一個Student類,那么,JVM就會創(chuàng)建一個Student對應(yīng)Class類的Class對象,該Class對象保存了Student類相關(guān)的類型信息。

Class類的對象作用是運行時提供或獲得某個對象的類型信息
反射的基本使用
獲取 Class 類對象
獲取反射中的Class對象有三種方法。
第一種,使用 Class.forName 靜態(tài)方法。
Class class1 = Class.forName("reflection.TestReflection");第二種,使用類的.class 方法
Class class2 = TestReflection.class;第三種,使用實例對象的 getClass() 方法。
TestReflection testReflection = new TestReflection();Class class3 = testReflection.getClass();

反射創(chuàng)造對象,獲取方法,成員變量,構(gòu)造器
本小節(jié)學(xué)習(xí)反射的基本API用法,如獲取方法,成員變量等。
反射創(chuàng)造對象
通過反射創(chuàng)建類對象主要有兩種方式:

實例代碼:
//方式一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);
運行結(jié)果:

反射獲取類的構(gòu)造器

看一個例子吧:
Class class1 = Class.forName("reflection.Student");Constructor[] constructors = class1.getDeclaredConstructors();for (int i = 0; i < constructors.length; i++) {System.out.println(constructors[i]);}

反射獲取類的成員變量

看demo:
// student 一個私有屬性age,一個公有屬性emailpublic 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);}}
運行結(jié)果:

即getField(String name) 根據(jù)參數(shù)變量名,返回一個具體的具有public屬性的成員變量,如果該變量不是public屬性,則報異常。
反射獲取類的方法

demo
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]);}}}
運行結(jié)果:

反射的實現(xiàn)原理
通過上一小節(jié)學(xué)習(xí),我們已經(jīng)知道反射的基本API用法了。接下來,跟著一個例子,學(xué)習(xí)反射方法的執(zhí)行鏈路。
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");}}
堆棧信息反映出反射調(diào)用鏈路:
java.lang.Exception: #666invoke target methodat 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)
invoke方法執(zhí)行時序圖

我們跟著反射鏈路去看一下源碼,先看Method的invoke方法:
public Object invoke(Object obj, Object... args)throws IllegalAccessException, IllegalArgumentException,InvocationTargetException{//校驗權(quán)限if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {Class<?> caller = Reflection.getCallerClass();checkAccess(caller, clazz, obj, modifiers);}}MethodAccessor ma = methodAccessor; // read volatileif (ma == null) {ma = acquireMethodAccessor(); //獲取MethodAccessor}//返回MethodAccessor.invokereturn ma.invoke(obj, args);}
由上可知道,Method 的 invoke 方法,其實是返回接口MethodAccessor的invoke方法。MethodAccessor接口有三個實現(xiàn)類,到底調(diào)用的是哪個類的 invoke 方法呢?

進(jìn)入acquireMethodAccessor方法,可以看到MethodAccessor由ReflectionFactory 的 newMethodAccessor方法決定。

再進(jìn)ReflectionFactory的newMethodAccessor方法,我們可以看到返回的是DelegatingMethodAccessorImpl對象,也就是說調(diào)用的是它的invoke方法。

再看DelegatingMethodAccessorImpl的invoke方法

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

因此,Method的invoke方法,是由本地方法invoke0決定的,再底層就是c++相關(guān)了,有興趣的朋友可以繼續(xù)往下研究。
反射的一些應(yīng)用以及問題
反射應(yīng)用
反射是Java框架的靈魂技術(shù),很多框架都使用了反射技術(shù),如spring,Mybatis,Hibernate等。
JDBC 的數(shù)據(jù)庫的連接
在JDBC連接數(shù)據(jù)庫中,一般包括加載驅(qū)動,獲得數(shù)據(jù)庫連接等步驟。而加載驅(qū)動,就是引入相關(guān)Jar包后,通過Class.forName() 即反射技術(shù),加載數(shù)據(jù)庫的驅(qū)動程序。
Spring 框架的使用
Spring 通過 XML 配置模式裝載 Bean,也是反射的一個典型例子。
裝載過程:
將程序內(nèi)XML 配置文件加載入內(nèi)存中
Java類解析xml里面的內(nèi)容,得到相關(guān)字節(jié)碼信息
使用反射機制,得到Class實例
動態(tài)配置實例的屬性,使用
這樣做當(dāng)然是有好處的:
不用每次都去new實例了,并且可以修改配置文件,比較靈活。
反射存在的問題
性能問題
java反射的性能并不好,原因主要是編譯器沒法對反射相關(guān)的代碼做優(yōu)化。
安全問題
我們知道單例模式的設(shè)計過程中,會強調(diào)將構(gòu)造器設(shè)計為私有,因為這樣可以防止從外部構(gòu)造對象。但是反射可以獲取類中的域、方法、構(gòu)造器,修改訪問權(quán)限。所以這樣并不一定是安全的。
看個例子吧,通過反射使用私有構(gòu)造器實例化。
public class Student {private String name;private Student(String name) {System.out.println("我是私有構(gòu)造器,我被實例化了");this.name = name;}public void doHomework(String subject) {System.out.println("我的名字是" + name);System.out.println("我在做"+subject+"作業(yè)");}}public class TestReflection {public static void main(String[] args) throws Exception {Class clazz = Class.forName("reflection.Student");// 獲取私有構(gòu)造方法對象Constructor constructor = clazz.getDeclaredConstructor(String.class);// true指示反射的對象在使用時應(yīng)該取消Java語言訪問檢查。constructor.setAccessible(true);Student student = (Student) constructor.newInstance("jay");student.doHomework("數(shù)學(xué)");}}
運行結(jié)果:

顯然,反射不管你是不是私有,一樣可以調(diào)用。所以,使用反射通常需要程序的運行沒有安全限制。如果一個程序?qū)Π踩杂袕娭埔螅詈貌灰褂梅瓷淅病?/span>

騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)
面試:史上最全多線程面試題 !
最新阿里內(nèi)推Java后端面試題
JVM難學(xué)?那是因為你沒認(rèn)真看完這篇文章

關(guān)注作者微信公眾號 —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識以及最新面試寶典


看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力
