:notebook: 本文已歸檔到:「blog」html
:keyboard: 本文中的示例代碼已歸檔到:「javacore」java
反射(Reflection)是 Java 程序開發語言的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。git
經過反射機制,能夠在運行時訪問 Java 對象的屬性,方法,構造方法等。github
反射的主要應用場景有:數據庫
類加載的完整過程以下:編程
(1)在編譯時,Java 編譯器編譯好 .java
文件以後,在磁盤中產生 .class
文件。.class
文件是二進制文件,內容是隻有 JVM 可以識別的機器碼。設計模式
(2)JVM 中的類加載器讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息。類加載器會根據類的全限定名來獲取此類的二進制字節流;而後,將字節流所表明的靜態存儲結構轉化爲方法區的運行時數據結構;接着,在內存中生成表明這個類的 java.lang.Class
對象。數組
(3)加載結束後,JVM 開始進行鏈接階段(包含驗證、準備、初始化)。通過這一系列操做,類的變量會被初始化。安全
要想使用反射,首先須要得到待操做的類所對應的 Class 對象。Java 中,不管生成某個類的多少個對象,這些對象都會對應於同一個 Class 對象。這個 Class 對象是由 JVM 生成的,經過它可以獲悉整個類的結構。因此,java.lang.Class
能夠視爲全部反射 API 的入口點。bash
反射的本質就是:在運行時,把 Java 類中的各類成分映射成一個個的 Java 對象。
舉例來講,假如定義瞭如下代碼:
User user = new User();
複製代碼
步驟說明:
new User()
,JVM 會根據 User
的全限定名去加載 User.class
。User.class
文件並加載 JVM 內存中。Class
對象,而且存儲在 JVM 的方法區。注意:一個類有且只有一個 Class
對象。Java 中的 java.lang.reflect
包提供了反射功能。java.lang.reflect
包中的類都沒有 public
構造方法。
java.lang.reflect
包的核心接口和類以下:
Member
接口 - 反映關於單個成員(字段或方法)或構造函數的標識信息。Field
類 - 提供一個類的域的信息以及訪問類的域的接口。Method
類 - 提供一個類的方法的信息以及訪問類的方法的接口。Constructor
類 - 提供一個類的構造函數的信息以及訪問類的構造函數的接口。Array
類 - 該類提供動態地生成和訪問 JAVA 數組的方法。Modifier
類 - 提供了 static 方法和常量,對類和成員訪問修飾符進行解碼。Proxy
類 - 提供動態地生成代理類和類實例的靜態方法。得到 Class 的三種方法:
(1)使用 Class 類的 forName
靜態方法
示例:
package io.github.dunwu.javacore.reflect;
public class ReflectClassDemo01 {
public static void main(String[] args) throws ClassNotFoundException {
Class c1 = Class.forName("io.github.dunwu.javacore.reflect.ReflectClassDemo01");
System.out.println(c1.getCanonicalName());
Class c2 = Class.forName("[D");
System.out.println(c2.getCanonicalName());
Class c3 = Class.forName("[[Ljava.lang.String;");
System.out.println(c3.getCanonicalName());
}
}
//Output:
//io.github.dunwu.javacore.reflect.ReflectClassDemo01
//double[]
//java.lang.String[][]
複製代碼
使用類的徹底限定名來反射對象的類。常見的應用場景爲:在 JDBC 開發中經常使用此方法加載數據庫驅動。
(2)直接獲取某一個對象的 class
示例:
public class ReflectClassDemo02 {
public static void main(String[] args) {
boolean b;
// Class c = b.getClass(); // 編譯錯誤
Class c1 = boolean.class;
System.out.println(c1.getCanonicalName());
Class c2 = java.io.PrintStream.class;
System.out.println(c2.getCanonicalName());
Class c3 = int[][][].class;
System.out.println(c3.getCanonicalName());
}
}
//Output:
//boolean
//java.io.PrintStream
//int[][][]
複製代碼
(3)調用 Object 的 getClass
方法,示例:
Object 類中有 getClass 方法,由於全部類都繼承 Object 類。從而調用 Object 類來獲取
示例:
package io.github.dunwu.javacore.reflect;
import java.util.HashSet;
import java.util.Set;
public class ReflectClassDemo03 {
enum E {A, B}
public static void main(String[] args) {
Class c = "foo".getClass();
System.out.println(c.getCanonicalName());
Class c2 = ReflectClassDemo03.E.A.getClass();
System.out.println(c2.getCanonicalName());
byte[] bytes = new byte[1024];
Class c3 = bytes.getClass();
System.out.println(c3.getCanonicalName());
Set<String> set = new HashSet<>();
Class c4 = set.getClass();
System.out.println(c4.getCanonicalName());
}
}
//Output:
//java.lang.String
//io.github.dunwu.javacore.reflect.ReflectClassDemo.E
//byte[]
//java.util.HashSet
複製代碼
判斷是否爲某個類的實例有兩種方式:
instanceof
關鍵字Class
對象的 isInstance
方法(它是一個 Native 方法)示例:
public class InstanceofDemo {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
if (arrayList instanceof List) {
System.out.println("ArrayList is List");
}
if (List.class.isInstance(arrayList)) {
System.out.println("ArrayList is List");
}
}
}
//Output:
//ArrayList is List
//ArrayList is List
複製代碼
經過反射來建立實例對象主要有兩種方式:
Class
對象的 newInstance
方法。Constructor
對象的 newInstance
方法。示例:
public class NewInstanceDemo {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<?> c1 = StringBuilder.class;
StringBuilder sb = (StringBuilder) c1.newInstance();
sb.append("aaa");
System.out.println(sb.toString());
//獲取String所對應的Class對象
Class<?> c2 = String.class;
//獲取String類帶一個String參數的構造器
Constructor constructor = c2.getConstructor(String.class);
//根據構造器建立實例
String str2 = (String) constructor.newInstance("bbb");
System.out.println(str2);
}
}
//Output:
//aaa
//bbb
複製代碼
Class
對象提供如下方法獲取對象的成員(Field
):
getFiled
- 根據名稱獲取公有的(public)類成員。getDeclaredField
- 根據名稱獲取已聲明的類成員。但不能獲得其父類的類成員。getFields
- 獲取全部公有的(public)類成員。getDeclaredFields
- 獲取全部已聲明的類成員。示例以下:
public class ReflectFieldDemo {
class FieldSpy<T> {
public boolean[][] b = {{false, false}, {true, true}};
public String name = "Alice";
public List<Integer> list;
public T val;
}
public static void main(String[] args) throws NoSuchFieldException {
Field f1 = FieldSpy.class.getField("b");
System.out.format("Type: %s%n", f1.getType());
Field f2 = FieldSpy.class.getField("name");
System.out.format("Type: %s%n", f2.getType());
Field f3 = FieldSpy.class.getField("list");
System.out.format("Type: %s%n", f3.getType());
Field f4 = FieldSpy.class.getField("val");
System.out.format("Type: %s%n", f4.getType());
}
}
//Output:
//Type: class [[Z
//Type: class java.lang.String
//Type: interface java.util.List
//Type: class java.lang.Object
複製代碼
Class
對象提供如下方法獲取對象的方法(Method
):
getMethod
- 返回類或接口的特定方法。其中第一個參數爲方法名稱,後面的參數爲方法參數對應 Class 的對象。getDeclaredMethod
- 返回類或接口的特定聲明方法。其中第一個參數爲方法名稱,後面的參數爲方法參數對應 Class 的對象。getMethods
- 返回類或接口的全部 public 方法,包括其父類的 public 方法。getDeclaredMethods
- 返回類或接口聲明的全部方法,包括 public、protected、默認(包)訪問和 private 方法,但不包括繼承的方法。獲取一個 Method
對象後,能夠用 invoke
方法來調用這個方法。
invoke
方法的原型爲:
public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 複製代碼
示例:
public class ReflectMethodDemo {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 返回全部方法
Method[] methods1 = System.class.getDeclaredMethods();
System.out.println("System getDeclaredMethods 清單(數量 = " + methods1.length + "):");
for (Method m : methods1) {
System.out.println(m);
}
// 返回全部 public 方法
Method[] methods2 = System.class.getMethods();
System.out.println("System getMethods 清單(數量 = " + methods2.length + "):");
for (Method m : methods2) {
System.out.println(m);
}
// 利用 Method 的 invoke 方法調用 System.currentTimeMillis()
Method method = System.class.getMethod("currentTimeMillis");
System.out.println(method);
System.out.println(method.invoke(null));
}
}
複製代碼
Class
對象提供如下方法獲取對象的構造方法(Constructor
):
getConstructor
- 返回類的特定 public 構造方法。參數爲方法參數對應 Class 的對象。getDeclaredConstructor
- 返回類的特定構造方法。參數爲方法參數對應 Class 的對象。getConstructors
- 返回類的全部 public 構造方法。getDeclaredConstructors
- 返回類的全部構造方法。獲取一個 Constructor
對象後,能夠用 newInstance
方法來建立類實例。
示例:
public class ReflectMethodConstructorDemo {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<?>[] constructors1 = String.class.getDeclaredConstructors();
System.out.println("String getDeclaredConstructors 清單(數量 = " + constructors1.length + "):");
for (Constructor c : constructors1) {
System.out.println(c);
}
Constructor<?>[] constructors2 = String.class.getConstructors();
System.out.println("String getConstructors 清單(數量 = " + constructors2.length + "):");
for (Constructor c : constructors2) {
System.out.println(c);
}
System.out.println("====================");
Constructor constructor = String.class.getConstructor(String.class);
System.out.println(constructor);
String str = (String) constructor.newInstance("bbb");
System.out.println(str);
}
}
複製代碼
數組在 Java 裏是比較特殊的一種類型,它能夠賦值給一個對象引用。下面咱們看一看利用反射建立數組的例子:
public class ReflectArrayDemo {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> cls = Class.forName("java.lang.String");
Object array = Array.newInstance(cls, 25);
//往數組裏添加內容
Array.set(array, 0, "Scala");
Array.set(array, 1, "Java");
Array.set(array, 2, "Groovy");
Array.set(array, 3, "Scala");
Array.set(array, 4, "Clojure");
//獲取某一項的內容
System.out.println(Array.get(array, 3));
}
}
//Output:
//Scala
複製代碼
其中的 Array 類爲 java.lang.reflect.Array
類。咱們經過 Array.newInstance
建立數組對象,它的原型是:
public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
return newArray(componentType, length);
}
複製代碼
動態代理是反射的一個很是重要的應用場景。動態代理常被用於一些 Java 框架中。例如 Spring 的 AOP ,Dubbo 的 SPI 接口,就是基於 Java 動態代理實現的。
靜態代理其實就是指設計模式中的代理模式。
代理模式爲其餘對象提供一種代理以控制對這個對象的訪問。
Subject 定義了 RealSubject 和 Proxy 的公共接口,這樣就在任何使用 RealSubject 的地方均可以使用 Proxy 。
abstract class Subject {
public abstract void Request();
}
複製代碼
RealSubject 定義 Proxy 所表明的真實實體。
class RealSubject extends Subject {
@Override
public void Request() {
System.out.println("真實的請求");
}
}
複製代碼
Proxy 保存一個引用使得代理能夠訪問實體,並提供一個與 Subject 的接口相同的接口,這樣代理就能夠用來替代實體。
class Proxy extends Subject {
private RealSubject real;
@Override
public void Request() {
if (null == real) {
real = new RealSubject();
}
real.Request();
}
}
複製代碼
說明:
靜態代理模式當然在訪問沒法訪問的資源,加強現有的接口業務功能方面有很大的優勢,可是大量使用這種靜態代理,會使咱們系統內的類的規模增大,而且不易維護;而且因爲 Proxy 和 RealSubject 的功能本質上是相同的,Proxy 只是起到了中介的做用,這種代理在系統中的存在,致使系統結構比較臃腫和鬆散。
爲了解決靜態代理的問題,就有了建立動態代理的想法:
在運行狀態中,須要代理的地方,根據 Subject 和 RealSubject,動態地建立一個 Proxy,用完以後,就會銷燬,這樣就能夠避免了 Proxy 角色的 class 在系統中冗雜的問題了。
Java 動態代理基於經典代理模式,引入了一個 InvocationHandler,InvocationHandler 負責統一管理全部的方法調用。
動態代理步驟:
com.sun.proxy.$ProxyXXXX
;InvocationHandler
實例 handler,用來處理 Proxy
全部方法調用;從上面能夠看出,JDK 動態代理的實現是基於實現接口的方式,使得 Proxy 和 RealSubject 具備相同的功能。
但其實還有一種思路:經過繼承。即:讓 Proxy 繼承 RealSubject,這樣兩者一樣具備相同的功能,Proxy 還能夠經過重寫 RealSubject 中的方法,來實現多態。CGLIB 就是基於這種思路設計的。
在 Java 的動態代理機制中,有兩個重要的類(接口),一個是 InvocationHandler
接口、另外一個則是 Proxy
類,這一個類和一個接口是實現咱們動態代理所必須用到的。
InvocationHandler
接口定義:
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
複製代碼
每個動態代理類都必需要實現 InvocationHandler
這個接口,而且每一個代理類的實例都關聯到了一個 Handler,當咱們經過代理對象調用一個方法的時候,這個方法的調用就會被轉發爲由 InvocationHandler
這個接口的 invoke
方法來進行調用。
咱們來看看 InvocationHandler 這個接口的惟一一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable 複製代碼
參數說明:
Method
對象若是不是很明白,等下經過一個實例會對這幾個參數進行更深的講解。
Proxy
這個類的做用就是用來動態建立一個代理對象的類,它提供了許多的方法,可是咱們用的最多的就是 newProxyInstance
這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException 複製代碼
這個方法的做用就是獲得一個動態的代理對象。
參數說明:
上面的內容介紹完這兩個接口(類)之後,咱們來經過一個實例來看看咱們的動態代理模式是什麼樣的:
首先咱們定義了一個 Subject 類型的接口,爲其聲明瞭兩個方法:
public interface Subject {
void hello(String str);
String bye();
}
複製代碼
接着,定義了一個類來實現這個接口,這個類就是咱們的真實對象,RealSubject 類:
public class RealSubject implements Subject {
@Override
public void hello(String str) {
System.out.println("Hello " + str);
}
@Override
public String bye() {
System.out.println("Goodbye");
return "Over";
}
}
複製代碼
下一步,咱們就要定義一個動態代理類了,前面說個,每個動態代理類都必需要實現 InvocationHandler 這個接口,所以咱們這個動態代理類也不例外:
public class InvocationHandlerDemo implements InvocationHandler {
// 這個就是咱們要代理的真實對象
private Object subject;
// 構造方法,給咱們要代理的真實對象賦初值
public InvocationHandlerDemo(Object subject) {
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args) throws Throwable {
// 在代理真實對象前咱們能夠添加一些本身的操做
System.out.println("Before method");
System.out.println("Call Method: " + method);
// 當代理對象調用真實對象的方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
Object obj = method.invoke(subject, args);
// 在代理真實對象後咱們也能夠添加一些本身的操做
System.out.println("After method");
System.out.println();
return obj;
}
}
複製代碼
最後,來看看咱們的 Client 類:
public class Client {
public static void main(String[] args) {
// 咱們要代理的真實對象
Subject realSubject = new RealSubject();
// 咱們要代理哪一個真實對象,就將該對象傳進去,最後是經過該真實對象來調用其方法的
InvocationHandler handler = new InvocationHandlerDemo(realSubject);
/* * 經過Proxy的newProxyInstance方法來建立咱們的代理對象,咱們來看看其三個參數 * 第一個參數 handler.getClass().getClassLoader() ,咱們這裏使用handler這個類的ClassLoader對象來加載咱們的代理對象 * 第二個參數realSubject.getClass().getInterfaces(),咱們這裏爲代理對象提供的接口是真實對象所實行的接口,表示我要代理的是該真實對象,這樣我就能調用這組接口中的方法了 * 第三個參數handler, 咱們這裏將這個代理對象關聯到了上方的 InvocationHandler 這個對象上 */
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.hello("World");
String result = subject.bye();
System.out.println("Result is: " + result);
}
}
複製代碼
咱們先來看看控制檯的輸出:
com.sun.proxy.$Proxy0
Before method
Call Method: public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
Hello World
After method
Before method
Call Method: public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
Goodbye
After method
Result is: Over
複製代碼
咱們首先來看看 com.sun.proxy.$Proxy0
這東西,咱們看到,這個東西是由 System.out.println(subject.getClass().getName());
這條語句打印出來的,那麼爲何咱們返回的這個代理對象的類名是這樣的呢?
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject
.getClass().getInterfaces(), handler);
複製代碼
可能我覺得返回的這個代理對象會是 Subject 類型的對象,或者是 InvocationHandler 的對象,結果卻不是,首先咱們解釋一下爲何咱們這裏能夠將其轉化爲 Subject 類型的對象?
緣由就是:在 newProxyInstance 這個方法的第二個參數上,咱們給這個代理對象提供了一組什麼接口,那麼我這個代理對象就會實現了這組接口,這個時候咱們固然能夠將這個代理對象強制類型轉化爲這組接口中的任意一個,由於這裏的接口是 Subject 類型,因此就能夠將其轉化爲 Subject 類型了。
同時咱們必定要記住,經過 Proxy.newProxyInstance
建立的代理對象是在 jvm 運行時動態生成的一個對象,它並非咱們的 InvocationHandler 類型,也不是咱們定義的那組接口的類型,而是在運行是動態生成的一個對象,而且命名方式都是這樣的形式,以$開頭,proxy 爲中,最後一個數字表示對象的標號。
接着咱們來看看這兩句
subject.hello("World");
String result = subject.bye();
複製代碼
這裏是經過代理對象來調用實現的那種接口中的方法,這個時候程序就會跳轉到由這個代理對象關聯到的 handler 中的 invoke 方法去執行,而咱們的這個 handler 對象又接受了一個 RealSubject 類型的參數,表示我要代理的就是這個真實對象,因此此時就會調用 handler 中的 invoke 方法去執行。
咱們看到,在真正經過代理對象來調用真實對象的方法的時候,咱們能夠在該方法先後添加本身的一些操做,同時咱們看到咱們的這個 method 對象是這樣的:
public abstract void io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.hello(java.lang.String)
public abstract java.lang.String io.github.dunwu.javacore.reflect.InvocationHandlerDemo$Subject.bye()
複製代碼
正好就是咱們的 Subject 接口中的兩個方法,這也就證實了當我經過代理對象來調用方法的時候,起實際就是委託由其關聯到的 handler 對象的 invoke 方法中來調用,並非本身來真實調用,而是經過代理的方式來調用的。
反射應用