反射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 - 也可以透過
ClassLoaderClassLoader 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