不學無數——Java動態代理

不學無數的程式設計師發表於2018-09-12

動態代理

1. 什麼是動態代理

在上一章節中,我們講的是代理其實都是靜態代理,動態代理是在執行階段動態的建立代理並且動態的處理對所代理方法的呼叫。在動態代理上所做的所有呼叫都會被重定向到單一的呼叫處理器中。在現在很流行的Spring中有一個AOP(面向切面)的其中核心實現技術就是動態代理的技術。

2. 為什麼要用動態代理

動態代理的優勢在於可以很方便的對代理類的函式進行統一的處理,而不用修改每個代理類中的方法。例如我們想計算出每一個方法的執行時間,如果使用靜態代理的話,那麼就需要在每一個代理類中進行更改,但是如果使用了動態代理可以對類的所有方法進行統一的管理。一處新增,所有方法適用。

3. 動態代理的簡單實現

3.1 靜態代理的實現

我們先看一下靜態代理的是如何實現的,關於靜態代理詳細的解釋可以看不學無數——Java代理模式,這裡只貼出關於靜態代理的一些程式碼。

Homeowner介面如下:

interface Homeowner{
    public void LeaseHouse(Home home);
}

複製程式碼

RealHomeowner類如下

class RealHomeowner implements Homeowner{

    @Override
    public void LeaseHouse(Home home) {
        System.out.println("房價是: "+ home.getPrice());
        System.out.println("房子顏色是: "+ home.getColor());
        System.out.println("房子出租成功");
    }
}

複製程式碼

代理類HomeProxy的實現

class HomeProxy implements Homeowner{

    private Homeowner homeowner;
    public HomeProxy(Homeowner homeowner){
        this.homeowner = homeowner;
    }

    @Override
    public void LeaseHouse(Home home) {
        System.out.println("中介干預");
        homeowner.LeaseHouse(home);
        System.out.println("中介干預完成");
    }
}

複製程式碼

在main方法中使用

public static void main(String[] args) {
    Home home = new Home("red",1000);
    RealHomeowner realHomeowner = new RealHomeowner();
    Homeowner homeowner = new HomeProxy(realHomeowner);
    homeowner.LeaseHouse(home);
}

複製程式碼

列印的資訊如下:

中介干預
房價是: 1000
房子顏色是: red
房子出租成功
中介干預完成

複製程式碼

3.2 動態代理的實現

在動態代理中是不需要代理類的,就是不需要上面靜態代理中的HomeProxy類,通過實現了InvocationHandler類,所有方法都由該Handler來處理了,意思就是所有被代理的方法都由InvocationHandler接管實際的處理任務。那麼看實際的例子

DynamicPro

class DynamicPro implements InvocationHandler{

    //真實被代理的例項物件
    private Object object;

    public DynamicPro(Object object){
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("中介干預");
        Object result = method.invoke(object,args);
        System.out.println("中介干預完成");
        return result;
    }
}

複製程式碼

在主方法如下的呼叫

public static void main(String[] args) {
        Home home = new Home("red",1000);
        //建立一個被代理的例項物件
        RealHomeowner realHomeowner = new RealHomeowner();
        //建立一個與被代理物件相關的InvocationHandler
        DynamicPro dynamicPro = new DynamicPro(realHomeowner);
        //建立一個類載入器
        ClassLoader classLoader = realHomeowner.getClass().getClassLoader();
        //被代理類的介面陣列,裡面的每一個方法都會執行InvocationHandler中的invoke方法
        Class<?>[] proxInterface = realHomeowner.getClass().getInterfaces();
        Homeowner homeowner = (Homeowner) Proxy.newProxyInstance(classLoader,proxInterface,dynamicPro);
        homeowner.LeaseHouse(home);
}
複製程式碼

列印如下

中介干預
房價是: 1000
房子顏色是: red
房子出租成功
中介干預完成
複製程式碼

4. 動態代理和靜態代理的區別

動態代理類圖

上面是關於動態代理的類圖,我們可以和靜態代理的類圖進行對比一下

靜態代理類圖

可以看到在動態代理中不需要了實際的代理角色類,因為實際的代理角色在動態代理中時動態生成的。在動態代理中增加了InvocationHandler介面類,這個介面中只有一個方法,就是invoke()方法。我們可以實現InvocationHandler類然後在invoke()方法中對呼叫實際方法時的前置或者後置處理。

5. 動態代理原理簡單分析

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

複製程式碼

通過上面的程式碼的Debug發現最後是通過下面程式碼返回一個物件的

 //返回構造器生成的例項物件
 return cons.newInstance(new Object[]{h});

複製程式碼

然後發現cons是從下面的程式碼獲得的

//獲得此代理類的構造器
final Constructor<?> cons = cl.getConstructor(constructorParams);

複製程式碼

cl是從下面的程式碼中獲得的

//查詢或生成指定的代理類
Class<?> cl = getProxyClass0(loader, intfs);
複製程式碼

而intfs是從下面的程式碼獲得

//克隆一個介面類
final Class<?>[] intfs = interfaces.clone();
複製程式碼

隨後想進去看getProxyClass0生成的代理類是什麼,但是發現進不去。後來查資料知道它由於是動態生成的,類是快取在java虛擬機器中的,可以通過下面的方法將類列印出來。


    public static void main(String[] args) {
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", new Class[] {Homeowner.class});
        String path = "/Users/hupengfei/git/Test/src/main/java/Practice/Day06/Homeowner.class";
        try(FileOutputStream fos = new FileOutputStream(path)) {
            fos.write(classFile);
            fos.flush();
            System.out.println("代理類class檔案寫入成功");
        } catch (Exception e) {
            System.out.println("寫檔案錯誤");
        }
    }

複製程式碼

對生成的class檔案進行反編譯,在Idea中能直接檢視


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

import Practice.Day06.Home;
import Practice.Day06.Homeowner;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements Homeowner {
    private static Method m1;
    private static Method m4;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void LeaseHouse(Home var1) throws  {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

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

    public final Homeowner getProxy() throws  {
        try {
            return (Homeowner)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  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("Practice.Day06.Homeowner").getMethod("getProxy");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

複製程式碼

首先我們可以先看生成的此類的建構函式

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }
    -----呼叫了父類的建構函式,而它的父類是Proxy類,父類的建構函式如下
    
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    --在父類中的h的定義如下
    protected InvocationHandler h;

複製程式碼

此時我們就知道為什麼我們的動態代理都會執行傳入的InvocationHandler中的invoke()方法了

在下面的靜態程式碼塊中我們發現了LeaseHouse()方法

 m4 = Class.forName("Practice.Day06.Homeowner").getMethod("LeaseHouse", Class.forName("Practice.Day06.Home"));

複製程式碼

然後在上面會發現有我們的方法

public final void LeaseHouse(Home var1) throws  {
    try {
        super.h.invoke(this, m4, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}

複製程式碼

此時我們再回想一下在InvocationHandler類中的invoke()方法中傳入的引數有Method方法了,這樣就可以將外部對於被代理物件的呼叫都轉化為呼叫invoke()方法,再由invoke()方法中呼叫被代理物件的方法。

動態代理類的位元組碼在程式執行時由Java反射機制動態生成

6. 參考文章

相關文章