反射Reflection、動態代理
尚硅谷JavaSE筆記-29
反射Reflection
- 反射Reflection是Java被視為動態語言的關鍵,可以在運行時獲取類的內部訊息
- 動態語言:程式運行時,代碼可以根據某些條件改變自身結構
- 具體來說,當我們加載完某類之後,在記憶體中堆的方法區就產生了一個該類的
Class物件
,此物件包含了完整的該類的結構訊息 - 因此我們能透過反射的API,從一個物件取得他所屬類的屬性與方法並進行各種操作
主要API
java.lang.Class
:代表一個類java.lang.reflect.Method
:代表類的方法java.lang.reflect.Field
:代表類的成員變數java.lang.reflect.Constructor
:代表類的構造器
Class類
Class類
即是"類的類",為反射的源頭- Object類中定義了
getClass()
方法,此方法被所有類繼承 Class類
的實例物件只能由系統建立,當一個.class檔案
被JVM載入執行時系統在堆的方法區產生唯一對應的物件- 通過
Class物件
可以完整獲取類的結構並進行各種操作,所以要使用反射必須先獲取Class物件
- 萬物皆物件、萬物皆有
Class類
- 數組的話只要元素類型與維度相同,視為同一個
Class
,例如int[5]
與int[20]
- 數組的話只要元素類型與維度相同,視為同一個
獲取Class實例物件
- 已知具體類名:
Class clazz = String.class
- 已知全類名:
Class clazz = Class.forName("java.lang.String")
- 可能拋出錯誤
ClassNotFoundException
- 也可以透過
ClassLoader
ClassLoader cl = this.getClass().getClassLoader()
Class clazz4 = cl.loadClass("全類名")
- 可能拋出錯誤
- 已有某類的實例物件:
Class clazz = obj01.getClass()
ClassLoader
補充知識點,有印象就好
類的加載實際步驟
- Load:將
.class檔案
讀入記憶體、創建Class物件
- Link:將類的二進位數據合併到JRE中、引入
常量
- Initialize:初始化,執行類構造器
<clinit>()
方法,進行static
屬性或指定初始值的賦值
- 如果一個類的父類還沒初始化,會先進行父類的初始化
- JVM會保證
<clinit>()
方法在多線程環境中的加鎖與同步
加載器的分類
- Bootstrap:引導類,由C語言編寫,負責Java平台核心庫,無法直接獲取
- Extension:擴展類,負責載入
jre/lib/ext或
指定目錄下的jar包
- System(APPs):系統類,負責載入
java.class.path
目錄下的jar包
,最常用的 - SomeClass:自訂類
從Class創立類的實例物件
- 取得一個
Class物件
後,可以透過newInstance()
方法創建對應類的實例,但有前提:- 該類必須有一個空參的構造器
- 該構造器要有符合的訪問權限
從Class實例物件反射獲取資訊
基本都是
getXXX()
,沒特別聲明的會返回包含父類的public
資訊
Declared
用於指明"當前代",並且無視權限修飾符
取得構造器
public Constructor<T>[] getConstructors()
:返回public
的構造器,構造器無法繼承,這可不會拿到父類的構造器public Constructor<T>[] getDeclaredConstructors
:返回當前類的所有構造器(含private)- 對於返回的
Constructor類物件
,還能再細探:public int getModifiers()
:取得修飾符public String getName()
:取得方法名稱public Class<?>[] getParameterTypes()
:取得參數的類型
取得屬性
-
public Field[] getFields()
:返回類或接口public的Field(含父類) -
public Field[] getDeclaredFields()
:返回當前的Field(含private) -
對於返回的
Field類物件
,還能再細探:-
public int getModifiers()
:以整數形式返回此Field的修飾符 -
public Class<?> getType()
:得到Field的屬性類型 -
public String getName()
:返回Field的名稱
-
取得方法
public Method[] getMethods()
:返回類public
的方法(含父類)public Method[] getDeclaredMethods()
:返回當前的方法(含private
)- 對於返回的
Method類物件
,還能再細探:public Class<?> getReturnType()
:取得全部的返回值public Class<?>[] getParameterTypes()
:取得全部的參數public int getModifiers()
:取得修飾符public Class<?>[] getExceptionTypes()
:取得異常資訊
獲取父類或實現的接口
public Class<?>[] getInterfaces()
:返回當前類所有實現的接口(不含父類)public Class<? Super T> getSuperclass()
:返回父類的Class物件
- 想獲得父類的接口可以2個組合用
其他資訊
框架常用到,尤其是泛型相關的
getDeclaredAnnotations()
:取得註解getGenericSuperclass
:獲取父類泛型類型TypeParameterizedType
:獲取泛型類型getActualTypeArguments
:獲取實際的泛型類型參數Package getPackage()
:獲取類所在的包
範例
自訂一個Person類,他的繼承、接口、註解這邊就不貼了
用來嘗試調用上面講的這些獲得資訊方法
@MyAnnotation(value = "hi")
public class Person extends Creature<String> implements Comparable<String>, MyInterface {
private String name;
int age;
public int id;
public Person() {
}
@Override
public String toString() {
return "name=" + name + ",id=" + id+ ",age=" + age;
}
@MyAnnotation(value = "name&int")
public Person(String name, int age) {
this.name = name;
this.age = age;
}
private String show(String nation) {
System.out.println("我的國籍是" + nation);
return nation;
}
public static void display() {
System.out.println("我是靜態方法");
}
@Override
public int compareTo(String o) {
return 0;
}
@Override
public void info() {
System.out.println("我是人");
}
}
使用反射
獲取資訊後,可以用返回的結構透過反射來操作物件實例
操作屬性
作法1
已知要調用的屬姓名
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
// 返回該屬性的Field物件
Field id = clazz.getField("id");
// 調用set方法
id.set(p, 20);
System.out.println(p);
// 查看可調用get方法,返回obj需要強轉
int nid = (int) id.get(p);
System.out.println(nid);
- 用
getField(String 屬性名)
方法獲得該屬性的Field物件 - 以Field物件調用set方法
- 受到權限管制,實際開發不這樣用
作法2
真正使用的
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
// 用Declared返回該屬性的Field物件
Field name = clazz.getDeclaredField("name");
// 開啟權限
name.setAccessible(true);
name.set(p, "Tom");
System.out.println(name.get(p));
- 已知要調用的屬姓名
- 用
getDeclaredField(String 屬性名)
方法獲得該屬性的Field物件,這次沒有權限問題了,總是能獲得 - 但是能獲得不等於能修改,想修改之前要先
setAccessible(true)
- 以Field物件調用set方法
調用方法
使用invoke
Class<Person> clazz = Person.class;
Person p = clazz.newInstance();
// 調用方法,需要指明方法的形參類型
Method show = clazz.getDeclaredMethod("show", String.class);
show.setAccessible(true);
Object nation = show.invoke(p, "台灣");
System.out.println("nation = " + nation);
// 調用靜態方法
Method display = clazz.getDeclaredMethod("display");
display.invoke(clazz);
- 已知要調用的方法名
- 用
getDeclaredMethod
,需要指明具體形參類型,因為可能有很多override
的方法 - 有權限問題一樣需要
setAccessible
invoke(物件, 形參...)
的返回值就是調用的方法的返回值- 如果沒有則返回
null
- 如果沒有則返回
- 也可以調用靜態方法
調用構造器
JDK8幾乎不這樣用,直接clazz.newInstance()就造物件了
但新版的JDK將該方法廢棄,建議用這種形式,所以還是學一下
Class<Person> clazz = Person.class;
// 調用構造器
Constructor<Person> con = clazz.getDeclaredConstructor(String.class, int.class);
con.setAccessible(true);
Person jay = con.newInstance("Jay", 50);
System.out.println(jay);
- 參數的部分要注意,我本來還以為
int.class
要用Integer.class
,結果不用
動態代理
一個動態的代理工廠,可以隨目標類創造出對應的代理實例
-
由靜態代理模式轉動態,需要解決兩個問題
-
如何根據加載到記憶體中的
被代理類
,動態的創建一個代理類
及實例物件
? -
當通過
代理類實例物件
調用方法a
時,如何動態的去調用被代理類
中的同名方法a
?
-
-
範例代碼:
interface Human {
String getBelief();
void eat(String food);
}
// 被代理類
class SuperMan implements Human {
@Override
public String getBelief() {
return "I believe I can fly!";
}
@Override
public void eat(String food) {
System.out.println("超人愛吃" + food);
}
}
// 代理工廠
class ProxyFactory {
// 調用此方法,返回一個代理類的物件
public static Object getProxyInstance(Object obj) { // obj=被代理類的物件
MyInvocationHandler myInvocationHandler = new MyInvocationHandler();
myInvocationHandler.bind(obj); // 連結物件
return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(),
myInvocationHandler);
}
}
// 代理類的物件被調用方法a時,就自動調用invoke方法
class MyInvocationHandler implements InvocationHandler {
private Object obj; // 需要使用被代理類的物件進行賦值
// 連結物件
public void bind(Object obj) {
this.obj = obj;
}
// 物件傳進來,在這裡進行反射調用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 要執行的方法就在這
return method.invoke(obj, args);
}
}
public class ProxyTest {
public static void main(String[] args) {
SuperMan superMan = new SuperMan();
Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
System.out.println(proxyInstance.getBelief());
proxyInstance.eat("漢堡包");
}
}
- 實現
InvocationHandler
接口,在invoke
方法中完成具體的代理操作 - 透過
Proxy.newProxyInstance
靜態方法,返回代理類的物件 - 以此物件完成想代理的操作
小結
- 獲取Class物件:
類.class
- 反射創造實例:
newInstance()
- 獲取資訊:
getXXX()
獲取public
及一些父類的getDeclaredXXX()
獲得當前類,無視封裝性
- 突破封裝權限:
setAccessible(true)
- 操作:
set
改變屬性、invoke
調用方法 - 動態代理:
Proxy.newProxyInstance
靜態方法、實現InvocationHandler
接口
上次修改於 2021-12-14