手機版
你好,游客 登錄 注冊
背景:
閱讀新聞

深入理解動態代理源碼

[日期:2019-10-05] 來源:cnblogs.com/wyq178  作者:Yrion [字體: ]

前言:  早期學習了動態代理在實際開發中的使用場景和使用方法,我們也知道了最經典的mybatis的mapper就是采用動態代理來實現的,那么動態代理的背后是怎樣的原理?為什么能實現動態代理?為什么動態代理只可以代理接口,而無法代理普通類?為什么動態代理需要傳入類的classLoder和接口?帶著這些疑問,我們來開啟本期的主題:探究動態代理的內部原理。

本篇博客的目錄

一:動態代理的基本使用方法

二:動態代理的內部運行過程

三:幾個相關的問題

四:總結

一:動態代理的基本使用方法

 1.1:簡單例子

首先我們來模擬一個簡單的動態代理的過程:某歌手去參加一個晚會,需要唱歌,他在演奏的過程中需要別人來報幕:演奏開始、演奏結束,每個歌手都遵循這樣的過程,在歌手進行表演的過程,穿插著主持人的開場白和結語,我們來用代碼模擬這個場景:

1.2:代理接口

首先我們來定義一個singer接口表示我們將要代理的接口:

 

public interface Singer {
    /**
    * 表演
    * @param soonName
    */
    public void perform(String soonName);
}

 

1.3:接口的具體實現類

 

public class Jay implements Singer {
 
    public void perform(String soonName) {
        System.out.println("接下來我為大家唱一首"+soonName);
    }
}

 

1.4:輔助類,用來模擬注冊人的前后臺詞

 

public class Presenter  {

    public void before(){
        System.out.println("請開始你的表演!");
    }

    public void after(){
        System.out.println("表演結束,大家鼓掌!");
    }
}

 

1.5:具體的代理類

  這里用proxy.newProxyInstance來創建一個代理類,傳入原始類的類加載器和接口與接口InvocationHandler,同時插入Presenter類的before與after方法,用于前置和后置處理

 

public class SingerProxy {

  private Presenter presenter;

  public SingerProxy(Presenter presenter){
      this.presenter = presenter;
  }
    /**
    * 獲取代理對象
    * @return
    */
    public Singer getProxy(){

        final Singer jay = new Jay();
        Singer singerProxy = (Singer)Proxy.newProxyInstance(jay.getClass().getClassLoader(), jay.getClass().getInterfaces(), new InvocationHandler() {」
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                presenter.before();
                method.invoke(jay, args);
                presenter.after();
                return null;
            }
        });
        return singerProxy;
    }
}

 

1.6:測試類

 

public class Test {
    public static void main(String[] args) {
        SingerProxy singerProxy = new SingerProxy(new Presenter());
        Singer proxy = singerProxy.getProxy();
        proxy.perform("《夜曲》");
    }
}

 

輸出:

 二:動態代理的內部探究

從上面的例子可以看出我們首先生成了一個代理類,然后用代理類來調用原始接口的方法,就可以實現我們的預設的邏輯,在原始接口的前后(或者出現異常的時候)插入我們想要的邏輯,那么究竟是為什么呢?

2.1:找到生成的代理類

我們如果需要打開生成的類,首先需要在測試類中添加這行代碼,設置系統屬性來保存生成的代理類的class文件:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

2.2:singerProxy類

通過動態代理生成的代理類名為:$Proxy0.class然后通過intelj idea反編譯之后源代碼是這樣的,這里主要看到有4個方法,method的m1\m2\m3\m0;分別由反射獲取的equals()、toString()、perform()、hashcode()方法,同時代理類繼承了proxy并且實現了原始Singer接口,重寫了perform()方法,所以這就解釋了為什么代理類可以調用perform()方法,在perform方法中,又調用了父類中的InvoationHander的invoke方法,并且傳入原始接口中的方法,而invoke方法在我們在創建代理類的時候重寫過,所以就會按照我們自定義的邏輯調用invoke方法,按照順序執行

 

import Java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import main.learn.proxy.Singer;

public final class $Proxy0 extends Proxy implements Singer {
    private static Method m1;
    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 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 void perform(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    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"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("main.learn.proxy.Singer").getMethod("perform", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

 

 2.3:生成代理類的過程

上面我們弄明白了,在代理類中會自動繼承原始接口類并且會調用InvocationHandler將接口類中的方法傳入進去,那么這個類是如何生成的呢?這就要翻生成代理類的源碼了:

 

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

 

上面的代碼由下來解釋:

2.3.1:安全管理器檢查與代理權限檢查

2.3.2:判斷接口的長度是否大于65535,大于不可往下進行。然后從WeakCache緩存中獲取代理類,如果找不到則通過proxyClassFactory生成代理類

2.3.2.1:生成代理類的class過程如下:

1??驗證傳入的interface是否可被傳入的classloader裝載

2??驗證傳入的是否是一個接口,如果不是一個接口,直接拋出IllegalArgumentException異常

3??判斷傳入的是否重復,這里是通過一個IdentityHashMap往里面put接口的class類,如果返回值不為null表示這個接口已經注冊過了(如果第一次put會返回null,按照傳統的做法是先get是否為null,如果不為null再put,這行代碼很妙省去了這個步驟),IdentityHashMap也是map的一種,不過它與我們普通的HashMap最大的不同在于它不會通過equals方法和hashCode方法去判斷key是否重復,而是通過==運算符

4??拼接代理類的名字固定為:com.sun.proxy.$Proxy+原子自增序號,為了防止并發調用,在生成代理類名字的時候,采用了AtomicLong的getAndIncrement方法去原子遞增代理類的序列號,這個方法是原子的,所以不會產生并發問題。這里也就是我們為什么看到最后的代理類是$Proxy0的原因(生成的代理類的序號是從0開始的)

5??調用ProxyGenerator.generateProxyClass方法來生成代理的class類(過程較為復雜、通過一些jvm指令去生成字節碼,包括遍歷方法類型、返回值、參數類型等)

6??通過defineClass將上一步產生的class字節碼生成class文件,該方法也是一個native方法,需要傳入類的classloader來進行裝載生成的類字節碼進而生成class

2.3.3: 通過反射獲取構造器constractor創建一個反射實例,這個過程進行了強制構造器的private私有化反射

三:幾個相關的問題

 3.1:為什么動態代理需要傳入classLoader?

  主要原因有以下幾個:

1??需要校驗傳入的接口是否可被當前的類加載器加載,假如無法加載,證明這個接口與類加載器不是同一個,按照雙親委派模型,那么類加載層次就被破壞了

2??需要類加載器去根據生成的類的字節碼去通過defineClass方法生成類的class文件,也就是說沒有類加載的話是無法生成代理類的

3.2:為什么動態代理需要傳入接口和只能代理接口?

 需要接口去通過ProxyGenerator類來生成代理類的字節碼,在生成的過程中,需要遍歷接口中的方法,包括方法簽名、參數類型、返回類型從而生成新的代理類,而代理類也需要繼承原始接口的方法,所以接口必須要傳

3.3:如果同一個接口創建多次代理會怎么辦?

在獲取代理對象的時候首先會從緩存(WeakCache)里面取,如果取不到才會通過代理工廠去創建,所以如果創建多個代理類的話,最終只會產生一個代理類

四:總結

    本篇博客通過一個動態代理的實際例子來分析了具體創建動態代理的過程,分析了動態代理的內部運行原理,以及分析了生成的代理類的源碼,動態代理在我們的開發過程中可謂是非常常見,比如最典型的mybatis的mapper代理原理、spring的aop實現原理,進行前置增強、后置增強等就是借助了動態代理。理解了動態代理能幫助我們進一步理解一些源碼,或許在以后的某些特定場景我們也可以采用動態代理來造出合適的輪子。

linux
相關資訊       動態代理源碼 
本文評論   查看全部評論 (0)
表情: 表情 姓名: 字數

       

評論聲明
  • 尊重網上道德,遵守中華人民共和國的各項有關法律法規
  • 承擔一切因您的行為而直接或間接導致的民事或刑事法律責任
  • 本站管理人員有權保留或刪除其管轄留言中的任意內容
  • 本站有權在網站內轉載或引用您的評論
  • 參與本評論即表明您已經閱讀并接受上述條款
彩票平台