1. 程式人生 > 其它 >通俗易懂講反射

通俗易懂講反射

可進入本人語雀文件看,格式更清晰明瞭哦
https://www.yuque.com/docs/share/3c013ec6-6c35-4854-aaf6-ff9a6e8a6af2?# 《通俗易懂講反射》

文章架構

反射是個啥

反射就是在執行期,可以獲取任意一個物件的屬性和方法,可以呼叫任意一個物件的方法和屬性。是一種動態獲取物件資訊以及動態呼叫物件的方法。最常見的場景就是在動態代理。而動態代理應用最廣的地方就是各種框架,比如:Spring

Class 物件

瞭解反射必定離不開 Class 物件。

爪窪國的人都知道,程式碼編寫完後是要編譯成 .class 檔案的。這裡的一個個 .class 檔案,被虛擬機器載入到記憶體後就是以 Class 物件存在,一個類只能有一個 .class 檔案,因此,一個類也就對應一個 Class 物件,Class 物件是類的描述資訊,比如:欄位、方法、註釋等等等等,只要你想得到的類結構,他在 Class 物件內都有體現,並且都能通過 Class 物件獲取到。

如何獲取 Class 物件引用

有以下兩種方式獲取:

  • 使用 Class 類的一個靜態方法 forName() 獲取:Class<?> clazz = Class.forName("className");
  • 通過類字面常量獲取:Class = String.class;

Class 類常用方法

這裡偷個懶,進入 class 檔案,通過 idea alt + 7 快捷鍵就可以展示類擁有的欄位、方法。

通過方法名其實就大概能猜出這個方法是幹嘛的了。

比如:

這裡的例子可能有點繞,就隨便拿一個類來舉例:比如 String 類。

String 類有一個描述其結構的 Class 物件,下面用 Class 來表示。

  • newInstance():通過 Class 物件例項化一個 String 物件。

  • getName():獲取 String 物件的類名

  • getInterfaces():獲取 String 物件所實現的所有介面資訊

  • getMethods():獲取 String 物件所有的方法資訊

代理

既然反射應用最廣的地方就是動態代理,那麼只介紹如何在執行中獲取一個類的結構就有點說不過去了。好歹也得介紹下動態代理吧。

動態代理

動態代理一共需要三個東西:

  • 介面,以下的程式碼是 Interface 介面

  • 實現介面的類,也是要被代理的類,以下的程式碼是 Clazz 類

  • 實現 InvocationHandler 介面的代理類,以下的程式碼是 ProxyHandler 類。

代理類就是通過現有的位元組碼,通過增強的方式對要呼叫的方法進行增強,最後動態生成新的位元組碼檔案,並且生成新的 .class 檔案,最後通過反射的方式執行增強的方法。

如下,ProxyHandler 類中的 invoke 方法就是增強後的方法,這裡在原來的被代理類執行前和執行後分別列印了兩行字串,實際應用場景可能是事務的開啟和提交。

在 DynamicProxyDemo 類的 main 方法裡面,通過 Proxy 類的靜態方法 newProxyInstance() 生成一個被增強後的代理類,newProxyInstance() 方法需要三個入參

  • 類載入器,隨便找個已經被載入的類獲取其類載入器即可

  • Class 陣列,被代理類所實現的介面陣列,由此可見,jdk 的動態代理必須實現介面

  • 實現 InvocationHandler 介面的代理類物件,必須把被代理的類傳入代理類中

獲取到增強後的代理類後,將其強轉成被代理類物件所實現的介面,然後呼叫方法,就可以發現,除了原本的實現,增強的邏輯也執行了。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface Interface {
    void m1();
    String m2();
    String m3(String a);
}

class Clazz implements Interface {
    @Override
    public void m1() {
        System.out.println("Clazz 的 m1() 方法");
    }

    @Override
    public String m2() {
        System.out.println("Clazz 的 m2() 方法");
        return "";
    }

    @Override
    public String m3(String a) {
        System.out.println("Clazz 的 m3() 方法");
        return a;
    }
}

class ProxyHandler implements InvocationHandler {
    private Object obj;

    public ProxyHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.printf("------ method:%s start proxy ------%n", method.getName());
        Object invoke = method.invoke(obj, args);
        System.out.printf("------ method:%s end proxy ------%n%n", method.getName());
        return invoke;
    }
}


public class DynamicProxyDemo {

    public static void main(String[] args) {
        Interface proxy = (Interface) Proxy.newProxyInstance(DynamicroxyDemo.class.getClassLoader(), new Class[]{Interface.class}, new ProxyHandler(new Clazz()));
        proxy.m1();
        proxy.m2();
        proxy.m3("Clazz 的 m3() 方法");
    }
}

執行結果:

------ method:m1 start proxy ------

Clazz 的 m1() 方法

------ method:m1 end proxy ------

------ method:m2 start proxy ------

Clazz 的 m2() 方法

------ method:m2 end proxy ------

------ method:m3 start proxy ------

Clazz 的 m3() 方法

------ method:m3 end proxy ------

靜態代理

既然都講了動態代理,那就來講講靜態代理吧。

動態代理是在執行時動態生成位元組碼,然後執行增強方法。

靜態代理也需要三個東西:

  • 介面

  • 實現介面的被代理類

  • 實現介面的代理類

其中被代理類實現介面後,實現了自己的程式碼邏輯。

代理類也實現了相同的介面,然後在對應的方法裡呼叫被代理類的方法就行了,需要增強方法的話在呼叫方法前或者呼叫方法後編寫增強程式碼即可,所以代理類需要持有一個被代理類的引用。程式碼十分簡單!看下面的例子。

interface StaticProxyInterface {
    void m1();
    String m2();
    String m3(String a);
}

class StaticProxyClazz implements StaticProxyInterface {
    @Override
    public void m1() {
        System.out.println("Clazz 的 m1() 方法");
    }

    @Override
    public String m2() {
        return "Clazz 的 m2() 方法";
    }

    @Override
    public String m3(String a) {
        return a;
    }
}

class FinalProxyClazz implements StaticProxyInterface {

    private StaticProxyInterface obj;

    public FinalProxyClazz(StaticProxyInterface obj) {
        this.obj = obj;
    }

    @Override
    public void m1() {
        System.out.println("------ start proxy ----");
        obj.m1();
    }

    @Override
    public String m2() {
        System.out.println("------ start proxy ----");
        return obj.m2();
    }

    @Override
    public String m3(String a) {
        System.out.println("------ start proxy ----");
        return obj.m3(a);
    }
}


public class StaticProxyDemo {

    public static void main(String[] args) {
        FinalProxyClazz finalProxyClazz = new FinalProxyClazz( new StaticProxyClazz());
        finalProxyClazz.m1();
        System.out.println(finalProxyClazz.m2());
        System.out.println(finalProxyClazz.m3("Clazz 的 m3() 方法"));
    }
}

執行結果:

------ start proxy ----

Clazz 的 m1() 方法

------ start proxy ----

Clazz 的 m2() 方法

------ start proxy ----

Clazz 的 m3() 方法

文章為本人學習過程中的一些個人見解,漏洞是必不可少的,希望各位大佬多多指教,幫忙修復修復漏洞!