JDK動態代理-使用及原理

2020-10-21 22:00:29

JDK動態代理

為了引出動態代理,我們看看一個案列!

廣東廣州,早上9:00,一位靚仔穿著人字拖、提著鳥籠,走進了早茶店。沒錯,這就是廣州典型的包租公!名下幾棟樓,只收租為生,沒工作,這人身真是無趣至極!

這裡就得出一個問題:收租不算工作?好吧,其實正真的包租公不會自己去收租,都是委託給中介去做。為什麼呢?這其中可以說牽扯到安全、隱私等等。想一下,假如包租公自己收租,當下租客很多,其他包租公就不爽了,乾脆找人去搗亂,比如只問不租,浪費包租公時間。當然不僅僅是這樣…


簡單使用

好的,租房中介就出來了,租客看房、談價、籤合同、交付鑰匙等等都讓中介(代理)去做,房東就坐等收錢就行了。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-Y6TnmfET-1603117818063)(D:\筆記\部落格\images\java\代理\jdk動態代理\租房.jpg)]

程式碼中使用輸出語句代替真正的業務!

首先,我們定義一個房東介面,和一個房東實現類。(為什麼需要介面?後面代理類是根據介面得到的,而且房東類不應該暴露出去的)

public interface ILandlordService {
    void deliver();
}
public class LandlordServiceImpl implements ILandlordService {
    public void deliver() {
        try {
            System.out.println("告知房東出租成功,房東收錢");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("出現異常,終止");
        }
    }
}

接下來建立一個類,實現java.lang.reflect.InvocationHandler介面,該類用來對原來的方法增強。(注意包,別導錯)

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

public class TransactionInvocationHandler implements InvocationHandler {
    // 需要被代理的物件(房東)
    private Object target;

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    /*
    	實現介面需要重寫的方法
        proxy:代理物件
        method:房東介面中被呼叫的方法
        args:房東介面中被呼叫的方法需要的引數
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object retVal = null;
        try {
            System.out.println("交付前與中介的交談");
            // 房東收錢
            retVal = method.invoke(target, args);
            System.out.println("租房成功");
            System.out.println("給鑰匙");
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("出現異常,終止交付");
        }
        return retVal;
    }
}

最後寫測試類(租客)

import org.junit.Test;
import java.lang.reflect.Proxy;

public class TenantTest {

    @Test
    public void test() {

        //這兩個建立物件和設定值應在spring中設定,讓spring建立、設值
        // 建立一個房東物件
        ILandlordService landlordService = new LandlordServiceImpl();
        // 建立增強類物件
        TransactionInvocationHandler transactionInvocationHandler = new TransactionInvocationHandler();
        // 把房東物件設定到增強類物件中使用
        transactionInvocationHandler.setTarget(landlordService);

        /*
             使用強轉得到房東類代理物件(中介)
             newProxyInstance方法需要三個引數
             ClassLoader loader:類載入器,直接使用本類的類載入器(系統類載入器)
             Class<?>[] interfaces:介面陣列
             InvocationHandler h:增強類
         */
        ILandlordService proxy = (ILandlordService) Proxy.newProxyInstance(
                this.getClass().getClassLoader(),
                // 這裡landlordService物件暫時在測試類中建立,
                // 使用spring後,注入到TransactionInvocationHandler(增強類)的欄位(target)中,
                // 直接到增強類中獲取,這樣測試類(租客)就不知道實現類了(房東)
                landlordService.getClass().getInterfaces(),
                transactionInvocationHandler
        );
        // 呼叫代理物件的deliver(),會執行增強類的invoke方法,引數method為代理物件呼叫的方法
        proxy.deliver();
    }
}
執行結果:
交付前與中介的交談
告知房東出租成功,房東收錢
租房成功
給鑰匙

此時很多人就疑惑了,怎麼建立代理類的?又怎麼會呼叫增強類的invoke方法的?那就接下去看看原理吧!建議截圖看,程式碼有點多!


原理

測試類中的newProxyInstance方法,點進去

ILandlordService proxy = (ILandlordService) Proxy.newProxyInstance(
    this.getClass().getClassLoader(),
    landlordService.getClass().getInterfaces(),
    transactionInvocationHandler
);

到proxy類的newProxyInstance方法,省去不必要的程式碼

@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h) {
    /*
    * Look up or generate the designated proxy class and its constructor.
    * 查詢或生成 指定的代理類和它的建構函式
    */
    /*
        獲取生成類的構造器
        loader:類載入器
        interfaces:介面陣列
    */
    Constructor<?> cons = getProxyConstructor(caller, loader, interfaces);
    return newProxyInstance(caller, cons, h);
}

往newProxyInstance方法中走,省去不必要的程式碼,try-catch也省去了

private static Object newProxyInstance(Class<?> caller,
                                           Constructor<?> cons,
                                           InvocationHandler h) {
    // caller有關安全的,這裡忽略
    checkNewProxyPermission(caller, cons.getDeclaringClass());
     /* 
        反射呼叫構造器建立這個類的物件
        cons:代理類構造器
        h:增強類
    */
    return cons.newInstance(new Object[]{h});
}

使用以下程式碼獲取代理類位元組碼檔案

ProxyGenerator 在 JDK 11 已經不是公有的了,我這裡的原始碼是JKD 11
下面獲取代理類位元組碼用的是JDK 8

import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;

public class DynamicProxyClassGenerator {
    public static void main(String[] args) throws Exception {
        // 實現類的class物件
        generateClassFile(LandlordServiceImpl.class, "LandlordServiceProxy");
    }

    public static void generateClassFile(Class<?> targetClass, String proxyName) throws Exception {
        // 根據類資訊和提供的代理類名稱,生成位元組碼
        byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces());
        String path = targetClass.getResource(".").getPath();
        System.out.println(path);
        FileOutputStream out = null;
        // 保留到硬碟中
        out = new FileOutputStream(path + proxyName + ".class");
        out.write(classFile);
        out.close();
    }
}

代理類生成的class檔案是這樣的,deliver()方法是根據我們定義的類得來的,省去幾個Object類得到的方法內容

呼叫有參構造器建立物件

import com.hao.service.ILandlordService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class LandlordServiceProxy extends Proxy implements ILandlordService {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

     // 建立物件時呼叫的方法
    public LandlordServiceProxy(InvocationHandler var1) throws  {
        // 呼叫父類別(proxy類)構造器
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
    }

    public final String toString() throws  {
    }

    public final void deliver() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.hao.service.ILandlordService").getMethod("deliver");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
protected Proxy(InvocationHandler h) {
    Objects.requireNonNull(h);
    // 把增強類設定到proxy類的欄位上
    this.h = h;
}
protected InvocationHandler h;

最終Proxy.newProxyInstance()返回的是代理類(中介)

ILandlordService proxy = (ILandlordService) Proxy.newProxyInstance(
);
proxy.deliver();

然後我們呼叫deliver()方法,進入的是代理類的deliver()方法

public final void deliver() throws  {
    try {
        // 呼叫父類別的h欄位(增強類)中的invoke方法,m3在本類static{}中獲取
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }
}

m3在static{}中

static {
    try {
        m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
        m2 = Class.forName("java.lang.Object").getMethod("toString");
        // 通過反射得到deliver方法
        m3 = Class.forName("com.hao.service.ILandlordService").getMethod("deliver");
        m0 = Class.forName("java.lang.Object").getMethod("hashCode");
    } catch (NoSuchMethodException var2) {
        throw new NoSuchMethodError(var2.getMessage());
    } catch (ClassNotFoundException var3) {
        throw new NoClassDefFoundError(var3.getMessage());
    }
}

可以看出,呼叫代理類最終會呼叫增強類的invoke方法。看懂了嗎?三連唄!!!