不學無數——Java動態代理

動態代理

1. 什麼是動態代理

在上一章節中,咱們講的是代理其實都是靜態代理,動態代理是在運行階段動態的建立代理而且動態的處理對所代理方法的調用。在動態代理上所作的全部調用都會被重定向到單一的調用處理器中。在如今很流行的Spring中有一個AOP(面向切面)的其中核心實現技術就是動態代理的技術。html

2. 爲何要用動態代理

動態代理的優點在於能夠很方便的對代理類的函數進行統一的處理,而不用修改每一個代理類中的方法。例如咱們想計算出每個方法的執行時間,若是使用靜態代理的話,那麼就須要在每個代理類中進行更改,可是若是使用了動態代理能夠對類的全部方法進行統一的管理。一處添加,全部方法適用。java

3. 動態代理的簡單實現

3.1 靜態代理的實現

咱們先看一下靜態代理的是如何實現的,關於靜態代理詳細的解釋能夠看不學無數——Java代理模式,這裏只貼出關於靜態代理的一些代碼。git

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);
}

打印的信息以下:ide

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

3.2 動態代理的實現

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

DynamicProui

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. 參考文章

相關文章
相關標籤/搜索