Java動態代理源碼詳解

一、概述

  前言:本文除了講解JDK動態代理及CGLIB動態代理實例和應用外,還會講解JDK動態代理源碼實現過程以及自己寫一手個JDK動態代理等。
  動態代理在很多底層框架中都會用得到,比如在Spring中用到的動態代理。它的作用很簡單,就是將你要使用的類,重新生成一個子類或本類。這樣框架就可以利用這個新生成的類做一些事情,比如在該類的方法前後加一些代碼。這樣的話,你就可以不用修改任何已經編寫好的代碼,只要使用代理就可以靈活的加入任何東西,將來不用了,也不會影響原來的代碼。
  代理模式使用代理對象完成用戶請求,屏蔽用戶對真實對象的訪問。現實世界的代理人被授權執行當事人的一些事宜,無需當事人出面,從第三方的角度看,似乎當事人並不存在,因爲他只和代理人通信。而事實上代理人是要有當事人的授權,並且在覈心問題上還需要請示當事人。
  使用代理模式的意圖還有,比如因爲安全原因需要屏蔽客戶端直接訪問真實對象,或者在遠程調用中需要使用代理類處理遠程方法調用的技術細節 (如 RMI),也可能爲了提升系統性能,對真實對象進行封裝,從而達到延遲加載的目的。


  Spring的AOP中用到了兩種動態代理。
  AOP的源碼中用到了兩種動態代理來實現攔截切入功能:JDK動態代理和CGLIB動態代理。兩種方法同時存在,各有優劣。JDK 的動態代理使用簡單,它內置在 JDK 中,因此不需要引入第三方 Jar 包,但相對功能比較弱。CGLIB 和 Javassist 都是高級的字節碼生成庫,總體性能比 JDK 自帶的動態代理好,而且功能十分強大。ASM 是低級的字節碼生成工具,使用 ASM 已經近乎於在使用 Java bytecode 編程,對開發人員要求最高,當然,也是性能最好的一種動態代理生成工具。但 ASM 的使用很繁瑣,而且性能也沒有數量級的提升,與 CGLIB 等高級字節碼生成工具相比,ASM 程序的維護性較差,如果不是在對性能有苛刻要求的場合,還是推薦 CGLIB 或者 Javassist。

  Spring選擇用JDK還是CGLIB的情況:
  (1)當Bean實現接口時,Spring就會用JDK的動態代理。
  (2)當Bean沒有實現接口時,Spring使用CGLIB實現。
  (3)可以強制使用CGLIB(在spring配置中加入< aop:aspectj-autoproxy proxy-target-class=“true”/>)。


  代理模式角色分爲 4 種:
  (1)主題接口:定義代理類和真實主題的公共對外方法,也是代理類代理真實主題的方法;
  (2)真實主題:真正實現業務邏輯的類;
  (3)代理類:用來代理和封裝真實主題;
  (4)Client:客戶端,使用代理類和主題接口完成一些工作;
  (5)動態代理類:以示區別,我們將動態生成的類叫做動態代理類。


  JDK動態代理
  在java的動態代理機制中,有兩個重要的類或接口,一個是 InvocationHandler(Interface),一個則是 Proxy(Class)類,這個類和接口是實現我們動態代理所必須用到的。主要用到了InvocationHandler類的Object invoke(Object proxy, Method method, Object[] args) throws Throwable方法,參數說明:
  proxy:指代我們所代理的那個真實對象,既動態生成的類的實例;
  method:指代的是主題接口的某個方法的Method對象;
  args:指代的是調用真實對象某個方法時接受的參數。


  兩種代理模式的區別:
  (1) JDK動態代理只能對實現了接口的類生成代理,而不能針對類。
  (2) CGLIB是針對類實現代理,主要是對指定的類生成一個子類,覆蓋其中的方法(繼承)。

  兩種代理模式的速度比較:
  (1)使用CGLIB實現動態代理,CGLIB底層採用ASM字節碼生成框架,使用字節碼技術生成動態代理類,比使用Java反射效率要高。唯一需要注意的是,CGLib不能對聲明爲final的方法進行代理,因爲CGLIB原理是動態生成被代理類的子類。
  (2)在對JDK動態代理與CGlib動態代理的代碼實驗中看,1W次執行下,JDK7及8的動態代理性能比CGlib要好20%左右。

二、實現示例及源碼解讀

2.1、延遲加載
  以一個簡單的示例來闡述使用代理模式實現延遲加載的方法及其意義。假設某客戶端軟件有根據用戶請求去數據庫查詢數據的功能。在查詢數據前,需要獲得數據庫連接,軟件開啓時初始化系統的所有類,此時嘗試獲得數據庫連接。當系統有大量的類似操作存在時 (比如 XML 解析等),所有這些初始化操作的疊加會使得系統的啓動速度變得非常緩慢。爲此,使用代理模式的代理類封裝對數據庫查詢中的初始化操作,當系統啓動時,初始化這個代理類,而非真實的數據庫查詢類,而代理類什麼都沒有做。因此,它的構造是相當迅速的。

  在系統啓動時,將消耗資源最多的方法都使用代理模式分離,可以加快系統的啓動速度,減少用戶的等待時間。而在用戶真正做查詢操作時再由代理類單獨去加載真實的數據庫查詢類,完成用戶的請求。這個過程就是使用代理模式實現了延遲加載。

  延遲加載的核心思想是:如果當前並沒有使用這個組件,則不需要真正地初始化它,使用一個代理對象替代它的原有的位置,只要在真正需要的時候纔對它進行加載。使用代理模式的延遲加載是非常有意義的,首先,它可以在時間軸上分散系統壓力,尤其在系統啓動時,不必完成所有的初始化工作,從而加速啓動時間;其次,對很多真實主題而言,在軟件啓動直到被關閉的整個過程中,可能根本不會被調用,初始化這些數據無疑是一種資源浪費。例如使用代理類封裝數據庫查詢類後,系統的啓動過程這個例子。若系統不使用代理模式,則在啓動時就要初始化 DelayLoadImpl 對象,而使用代理模式後,啓動時只需要初始化一個輕量級的對象 DelayLoadProxy 。

/**
 * 接口類(主題接口)
 */
public interface IDelayLoad {
    String query();
}
/**
 * 實現類(真實主題)
 */
public class DelayLoadImpl implements IDelayLoad {
    // 構造方法
    public DelayLoadImpl() {
        try {
            // 假設數據庫連接等耗時操作
            Thread.sleep(1000);
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }

    @Override
    public String query() {
        return "query string";
    }
}
/**
 * 代理類
 */
public class DelayLoadProxy implements IDelayLoad {

    private DelayLoadImpl real = null;

    @Override
    public String query() {
        // 在真正需要的時候才能創建真實對象,創建過程可能很慢
        if (real == null) {
            real = new DelayLoadImpl();
        }
        // 在多線程環境下,這裏返回一個虛假類,類似於Future模式
        return real.query();
    }
}
/**
 * 測試類
 */
public class Test {
    public static void main(String[] args) {
        // 使用代理
        IDelayLoad iDelayLoad = new DelayLoadProxy();
        // 在真正使用時才創建真實對象
        iDelayLoad.query();
    }
}

// 運行結果
query string

2.2、JDK動態代理

/**
 * 接口類(主題接口)
 */
public interface Person {
    /**
     * 相親
     */
    void findLove();
}
/**
 *  實現類(真實主題),單身女孩
 */
public class SingleGirl implements Person {
    private String sex = "女";
    private String name = "小蘿莉";
    @Override
    public void findLove() {
        System.out.println("我叫:" + this.name + ",性別:" + this.sex + ",我找對象的要求並不高:");
        System.out.println("1.基本要求是有房有車的,沒有就免談;");
        System.out.println("2.然後長相風流倜儻,英俊瀟灑,身高要求180cm以上,體重70kg左右。");
    }

    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
/**
 * 代理類,代理人
 */
public class DynamicProxy implements InvocationHandler {
    /**
     * 被代理對象的引用
     */
    private Person target;
    /**
     * 獲取代理對象
     */
    public Object getInstance(Person target) throws Exception {
        this.target = target;
        Class<? extends Person> clazz = target.getClass();
        System.out.println("被代理對象的class是:" + clazz);
        // 傳入被代理對象SingleGirl的ClassLoader和接口數組(接口可多實現),以及代理類本身實例
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是中間人代理人:開始進行海選優質異性...");
        System.out.println("------------------------");
        // 反射調用
        method.invoke(this.target, args);
        // 這樣就是直接調用了
        // target.findLove();
        System.out.println("------------------------");
        System.out.println("我是中間人代理人:已經安排他們約會...");
        return null;
    }
}
/**
 * 測試類
 */
public class TestFindLove {
    public static void main(String[] args) {
        try {
            Person obj = (Person) new DynamicProxy().getInstance(new SingleGirl());
            System.out.println("實際的的class是:" + obj.getClass());
            // 動態生成的代理類對象進行調用方法
            obj.findLove();     
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 運行結果
被代理對象的class是:class com.test.jdkdemo.SingleGirl
實際的的class是:class com.sun.proxy.$Proxy0
我是中間人代理人:開始進行海選優質異性...
------------------------
我叫:小蘿莉,性別:女,我找對象的要求並不高:
1.基本要求是有房有車的,沒有就免談;
2.然後長相風流倜儻,英俊瀟灑,身高要求180cm以上,體重70kg左右。
------------------------
我是中間人代理人:已經安排他們約會...

  從運行結果發現,實際生成的對象obj調用findLove()方法,而這個對象所屬的類是:

class com.sun.proxy.$Proxy0

  說明動態生成的類是$Proxy,它來代理被代理的類執行某些操作,下面將進行解讀。


2.2.1、源碼解讀
  現在從測試類開始,帶你一步一步解讀JDK動態代理底層原理實現是如何實現的,以下開始是源碼講解部分,各個類之間是相互貫通的,請結合在一起看。

// 1、測試類:
public class TestFindLove {
    public static void main(String[] args) {
        // 傳入SingleGirl對象,返回Person類型的對象
        Person obj = (Person) new DynamicProxy().getInstance(new SingleGirl());
        // 動態生成的類$Proxy0的實例進行調用方法
        obj.findLove();
    }
}
// 2、InvocationHandler源碼
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
// 3、DynamicProxy代理類:
public class DynamicProxy implements InvocationHandler {
    private Person target;
    /**
     * 獲取動態代理類對象實例
     * @param target Person類型的實現類
     */
    public Object getInstance(Person target) throws Exception {
        // 將成員變量target賦值爲SingleGirl對象
        this.target = target;
        Class<? extends Person> clazz = target.getClass();
        // Proxy類的方法,傳入SingleGirl類的class對象以及它實現的接口數組,以及代理類DynamicProxy本身的實例
        // 其實這步就是生成動態代理類的class文件加載到JVM後並返回它的實例
        return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("我是中間人代理人:開始進行海選優質異性...");
        System.out.println("------------------------");
        // 反射調用,method:反射生成的Person的findLove()方法的Method,
        // this.target:在getInstance()方法中已經將之指向類SingleGirl的實例了
        method.invoke(this.target, args);
        System.out.println("------------------------");
        System.out.println("我是中間人代理人:已經安排他們約會...");
        return null;
    }
}
// 4、Proxy源碼:在此類中,生成了動態類Proxy0,並且可以返回它的實例
public class Proxy implements java.io.Serializable {
    // InvocationHandler類型的引用
    protected InvocationHandler h;
    // 構造方法
    protected Proxy(InvocationHandler h) {
        Objects.requireNonNull(h);
        this.h = h;
    }
    // 在代理類DynamicProxy的getInstance()方法裏面調用此方法
    public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException {
        final Class<?>[] intfs = interfaces.clone();
        // Look up or generate the designated proxy class.
        // 查找或生成指定的代理類(動態代理類)
        // 傳入的是SingleGirl的class對象以及接口數組,返回的是動態類$Proxy0的Class對象cl
        Class<?> cl = getProxyClass0(loader, intfs);
        // 用動態代理類的Class對象獲取動態代理類$Proxy0的Constructor對象cons
        final Constructor<?> cons = cl.getConstructor(constructorParams);
        // 通過反射,獲取動態代理類$Proxy0的實例,並new了Object數組,裏面是InvocationHandler類型的實例,既是前面的DynamicProxy
        // 所以在這裏的時候,執行了動態代理類$Proxy0的構造方法,返回$Proxy0的實例,那裏面是怎麼執行的,請繼續往下看
        return cons.newInstance(new Object[]{h});
    }

    // 查找或生成指定的代理類(動態代理類)
    private static Class<?> getProxyClass0(ClassLoader loader,Class<?>... interfaces) {
        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        // 如果存在實現了給定interfaces的和給定ClassLoader定義的代理類(動態代理類),就直接返回緩存的副本;
        // 否則,它將通過ProxyClassFactory創建代理類(動態代理類)

        // 發現是下面的WeakCache<K, P, V>類的方法
        return proxyClassCache.get(loader, interfaces);
    }
}
// 5、WeakCache<K, P, V>類:
public V get(K key, P parameter) {
    // create subKey and retrieve the possible Supplier<V> stored by that
    // subKey from valuesMap
    // 創建subKey並從valuesMap中檢索由該subKey存儲的可能的Supplier <V>

    // 發現subKeyFactory.apply()是下面BiFunction<T, U, R>接口的方法
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    Supplier<V> supplier = valuesMap.get(subKey);
}
// 6、然而Proxy類的內部類ProxyClassFactory,實現了BiFunction<T, U, R>接口,所以是此類在生成動態代理類:
private static final class ProxyClassFactory implements BiFunction<ClassLoader, Class<?>[], Class<?>> {
    // prefix for all proxy class names
    // 所有代理類(動態代理類)名的前綴
    private static final String proxyClassNamePrefix = "$Proxy";

    // next number to use for generation of unique proxy class names
    // 用於生成唯一代理類(動態代理類)名稱的下一個數字
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
        Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
        /*
         * Choose a name for the proxy class to generate.
         */
        // 生成代理類(動態代理類)的名稱的後綴數字
        long num = nextUniqueNumber.getAndIncrement();
        String proxyName = proxyPkg + proxyClassNamePrefix + num;
        /*
         * Generate the specified proxy class.
         */
        // 生成指定的代理類(動態代理類)
        // 此處用sun.misc.ProxyGenerator類的generateProxyClass()方法來生成代理類(動態代理類)的字節碼
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
        return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
    }
}
// 7、$Proxy0動態代理類:先別管怎麼獲得它的
// 這個類是【動態】生成的,繼承了Proxy類,並且實現了Person接口,類和方法都用final修飾,類不可被繼承、方法不能被重寫
public final class $Proxy0 extends Proxy implements Person {
    // 這裏有每個方法的引用,請看下面的靜態代碼塊
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    // 注意此構造方法,是在前面講到的Proxy類的newProxyInstance()方法中cons.newInstance(new Object[]{h})
    // 這段代碼執行的時候,反射調用進行實例化的,並且參數是代理類DynamicProxy的實例,就是調用了此構造方法

    // 而且還調用了super(paramInvocationHandler),即在Proxy類的構造方法中把paramInvocationHandler賦值給this.h
    // 所以這個過程就是在$Proxy0的構造方法中將DynamicProxy的實例通過構造方法傳給父類Proxy中的InvocationHandler類型的引用this.h
    public $Proxy0(InvocationHandler paramInvocationHandler){
        super(paramInvocationHandler);
    }

    // 重寫了equals()方法
    public final boolean equals(Object paramObject) {
        try {
            return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
        } catch (Error|RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    // 實現了Person接口的方法
    public final void findLove() {
        try {
            // 由以上得出:
            // this.h:看Proxy的源碼,發現h就是Proxy類中的InvocationHandler h,並且在指向了DynamicProxy實例
            // this:指此動態代理類$Proxy0本身
            // m3:看下面靜態代碼塊,指的就是反射生成的Person接口的findLove()方法的Method對象

            // 因此,this.h.invoke(this, m3, null)其實就是在動態代理類$Proxy0中用DynamicProxy實例調用它的成員方法invoke()
            // 此方法並不是反射的invoke()方法,只是普通的調用,調用時就將動態代理類$Proxy0本身、m3爲Person反射的Method
            // 傳到了DynamicProxy類的invoke()方法裏面,然後就用m3.invoke(this.target, args)真正的反射調用了
            // 其中this.target是SingleGirl實例,它在一開始就實例化了
            // 然後在反射調用的代碼前後,可以插入自定義的一些代碼
            this.h.invoke(this, m3, null);
            return;
        } catch (Error|RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    // 重寫了toString()方法
    public final String toString() {
        try {
            return (String)this.h.invoke(this, m2, null);
        } catch (Error|RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }
    // 重寫了hashCode()方法
    public final int hashCode() {
        try {
            return ((Integer)this.h.invoke(this, m0, null)).intValue();
        } catch (Error|RuntimeException localError) {
            throw localError;
        } catch (Throwable localThrowable) {
            throw new UndeclaredThrowableException(localThrowable);
        }
    }

    static {
        try {
            // 每個方法的引用向反射生成的方法的Method對象
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
            m3 = Class.forName("com.test.jdkdemo.Person").getMethod("findLove", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        } catch (NoSuchMethodException localNoSuchMethodException) {
            throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
        } catch (ClassNotFoundException localClassNotFoundException) {
            throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
        }
    }
}

總結:
  動態代理,顧名思義,就是動態地生成一個動態代理類,比如在這個例子中,是com.sun.proxy.$Proxy0。
實現過程原理:
  1、動態代理類拿到被代理對象(主題接口類型)的引用及真實接口的實例,然後獲取它所有實現的接口。

  2、JDK代理重新生成一個類,繼承java.lang.reflect.Proxy,同時實現代理類所實現的接口(如Person類)。

  3、然後將這個類的class字節碼文件加載到JVM,就可以對這個動態生成的類進行使用,
  比如實例化以及將InvocationHandler類型的引用指向代理類(如DynamicProxy)實例。

  4、外界拿到的就是動態代理生成的類(如$Proxy0)的實例,去調用它實現接口的方法(如findLove()方法)
  然而在方法裏面,是用代理類(如DynamicProxy)的實例去調用它的成員方法invoke(),
  並且將接口Person的某個方法的Method傳入invoke()方法內,而真實主題(如SingleGirl)實例在一開始就獲得了
  所以就可以用Method來反射調用真實主題的方法,反射調用的前後,可以插入自定義的代碼。

如何獲取字節碼文件:
  獲取動態代理類$Proxy0的字節碼文件,將之輸出到當前項目的class文件的當前包下:

/**
 * 獲取動態代理類$Proxy0的字節碼並加載到JVM
 */
public class GenerateProxyClass {
    public static void main(String[] args) throws IOException {
        // Proxy類的內部類ProxyClassFactory就是這樣生成字節碼文件的
        // 爲什麼是這個類,在上面有詳細講解
        byte[] data = ProxyGenerator.generateProxyClass("$Proxy0", new Class[] { Person.class });
        FileOutputStream os = new FileOutputStream("D:/workspace/proxy/target/classes/com/proxy/jdk/$Proxy0.class");
        os.write(data);
        os.close();
    }
}

生成如下圖所示,然後將之拖拽到jd-gui.exe(一款反編譯軟件)下,就可以看到源碼了。
這裏寫圖片描述

部分反編譯後的代碼如下圖所示:
這裏寫圖片描述


2.2.2、自己實現JDK動態代理
  前面講了那麼多原理,現在我們自己來實現JDK動態代理。

/**
 * 實現類(真實主題:真正實現業務邏輯的類),單身女孩
 */
public class TXSingleGirl implements Person {
    @Override
    public void findLove() {
        System.out.println("[TXSingleGirl]我叫:小蘿莉,性別:女,我找對象的要求並不高:");
        System.out.println("[TXSingleGirl]1.基本要求是有房有車的,沒有就免談;");
        System.out.println("[TXSingleGirl]2.然後長相風流倜儻,英俊瀟灑,身高要求180cm以上,體重70kg左右。");
    }
}
/**
 * 自定義InvocationHandler
 */
public interface TXInvocationHandler {
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
/**
 * 自定義ClassLoader,做用是將class文件加載到JVM
 */
public class TXClassLoader extends ClassLoader{

    private File resourceFile;

    public TXClassLoader(){
        // 獲取當前class文件的包路徑
        String basePath = TXClassLoader.class.getResource("").getPath();
        this.resourceFile = new File(basePath);
    }

    /**
     * 將class文件加載到JVM
     */
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        // 獲得包名拼接類名--->全限定名
        String className = TXClassLoader.class.getPackage().getName() + "." + name;
        if(resourceFile != null){
            File classFile = new File(resourceFile,name.replaceAll("\\.", "/") + ".class");
            if(classFile.exists()){
                FileInputStream in = null;
                ByteArrayOutputStream out = null;
                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte [] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1) {
                        out.write(buff, 0, len);
                    }
                    // 加載到JVM
                    return defineClass(className, out.toByteArray(), 0,out.size());
                }catch (Exception e) {
                    e.printStackTrace();
                }finally{
                    if(null != in){
                        try {
                            in.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if(null != out){
                        try {
                            out.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    // 刪除class文件
                    classFile.delete();
                }
            }
        }
        return null;
    }
}
/**
 * 自定義Proxy
 * 它的作用是:生成動態代理類的源代碼
 * (在JDK實現時,還繼承Proxy父類,因爲用到了其中的InvocationHandler h引用,在此爲了簡化,直接寫死)
 * 然後編譯成class文件,再加載到JVM並可使用,然後通過反射生成動態代理類實例
 */
public class TXProxy {
    // 換行符
    private static String ln = "\r\n";

    /**
     * 自定義newProxyInstance方法,傳入自定義TXClassLoader
     * @param classLoader TXClassLoader
     * @param interfaces Class<?>[]
     * @param h TXInvocationHandler
     * @return
     */
    public static Object newProxyInstance(TXClassLoader classLoader, Class<?>[] interfaces, TXInvocationHandler h) {
        try {
            // 1、生成源代碼
            String proxySrc = generateSrc(interfaces[0]);
            // 2、將生成的源代碼輸出到磁盤,保存爲.java文件
            // 獲取當前文件路徑
            String filePath = TXProxy.class.getResource("").getPath();
            File file = new File(filePath + "$Proxy0.java");
            FileWriter writer = new FileWriter(file);
            writer.write(proxySrc);
            writer.flush();
            writer.close();

            // 3、編譯源代碼,並且編譯生成.class文件
            // 此處如果返回null,由於設置JAVA_HOME爲java\jdk,所以是找不到tools.jar的,需要將jdk/lib下將tools.jar複製到jre/lib下
            // 如果是Maven配置是JDK編譯插件,請換成本地的JDK,這樣才能找到tools.jar
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(file);
            CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();

            // 4.通過TXClassLoader將class文件中的內容,動態加載到JVM中來
            Class proxyClass = classLoader.findClass("$Proxy0");

            // 5.返回被代理後的代理對象
            Constructor instance = proxyClass.getConstructor(TXInvocationHandler.class);
            // 刪除源碼文件
            file.delete();
            return instance.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成源代碼
     * @param interfaces
     * @return
     */
    private static String generateSrc(Class<?> interfaces) {
        StringBuffer src = new StringBuffer();
        src.append("package com.tanxl.proxy.custom;" + ln);
        src.append("import java.lang.reflect.Method;" + ln);
        src.append("public class $Proxy0 implements " + interfaces.getName() + "{" + ln);
        src.append("TXInvocationHandler h;" + ln);
        src.append("public $Proxy0(TXInvocationHandler h) {" + ln);
        src.append("this.h = h;" + ln);
        src.append("}" + ln);
        for (Method m : interfaces.getMethods()) {
            src.append("public " + m.getReturnType().getName() + " " + m.getName() + "(){" + ln);
            src.append("try{" + ln);
            src.append("Method m = " + interfaces.getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});"
                    + ln);
            src.append("this.h.invoke(this,m,null);" + ln);
            src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
            src.append("}" + ln);
        }
        src.append("}");
        return src.toString();
    }
}
/**
 * 測試類
 */
public class TestCustomProxy {
    public static void main(String[] args) throws Exception {
        Person obj = (Person)new TXDynamicProxy().getInstance(new TXSingleGirl());
        System.out.println("實際的的class是:" + obj.getClass());
        obj.findLove();
    }
}
// 運行結果
被代理對象的class是:class com.tanxl.proxy.custom.TXSingleGirl
實際的的class是:class com.tanxl.proxy.custom.$Proxy0
[TXDynamicProxy]我是中間人代理人:開始進行海選優質異性...
------------------------
[TXSingleGirl]我叫:小蘿莉,性別:女,我找對象的要求並不高:
[TXSingleGirl]1.基本要求是有房有車的,沒有就免談;
[TXSingleGirl]2.然後長相風流倜儻,英俊瀟灑,身高要求180cm以上,體重70kg左右。
------------------------
[TXDynamicProxy]我是中間人代理人:已經安排他們約會...

上面具體過程有些是簡化了的,具體過程就不講解了,需要注意的是,如果報空指針異常,需要將jdk/lib下將tools.jar複製到jre/lib下。


2.3、CGLIB動態代理
  JDK中的動態代理通過反射類Proxy和InvocationHandler回調接口實現,要求動態代理類必須實現一個接口,只能對該類接口中定義的方法實現代理,這在實際編程中有一定的侷限性。
  使用CGLIB[Code Generation Library]實現動態代理,並不要求動態代理類必須實現接口,底層採用ASM字節碼生成框架生成代理類的字節碼,下面使用CGLib如何實現動態代理。

/**
 * 主題類
 */
public class SingleMan {
    public void findGirl() {
        System.out.println("我叫:小豬佩奇,性別:男,我找對象的要求並不高:");
        System.out.println("1.基本要求是有房有車的,沒有就免談;");
        System.out.println("2.然後膚白貌美大長腿,身高要求170cm及以上,體重50kg左右。");
    }
}
/**
 * 代理類,實現MethodInterceptor接口,定義方法的攔截器
 */
public class CglibProxy implements MethodInterceptor {

    public Object getInstance(Class clazz) throws Exception {
        Enhancer enhancer = new Enhancer();
        // 這一步就是告訴Cglib,生成的子類需要繼承哪個類
        enhancer.setSuperclass(clazz);
        // 設置回調
        enhancer.setCallback(this);
        // 第一步、生成源代碼
        // 第二步、編譯成class文件
        // 第三步、加載到JVM中,並返回動態生成的代理對象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("我是中間人代理人:開始進行海選優質異性...");
        System.out.println("------------------------");
        // 這個obj的引用是由CGLib給new出來的,它是被代理對象的子類(繼承了自己寫的那個類)
        // 在new子類之前,實際上默認先調用了super()方法的
        // new了子類的同時,先new出來父類,這就相當於是間接的持有了我們父類的引用
        // 子類重寫了父類的所有的方法
        // 我們改變子類對象的某些屬性,是可以間接的操作父類的屬性的
        proxy.invokeSuper(obj, args);
        System.out.println("------------------------");
        System.out.println("我是中間人代理人:已經安排他們約會...");
        return null;
    }
}
/**
 * 測試類
 */
public class Test {
    public static void main(String[] args) {
        // JDK的動態代理是通過接口來進行強制轉換的
        // 生成以後的代理對象,可以強制轉換爲接口

        // CGLib的動態代理是通過生成一個被代理對象的子類,然後重寫父類的方法
        // 生成以後的對象,可以強制轉換爲被代理對象(也就是用自己寫的類)的類型
        try {
            SingleMan obj = (SingleMan) new CglibProxy().getInstance(SingleMan.class);
            obj.findGirl();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// 運行結果
我是中間人代理人:開始進行海選優質異性...
------------------------
我叫:小豬佩奇,性別:男,我找對象的要求並不高:
1.基本要求是有房有車的,沒有就免談;
2.然後膚白貌美大長腿,身高要求170cm及以上,體重50kg左右。
------------------------
我是中間人代理人:已經安排他們約會...

三、代理模式應用場合

3.1、遠程代理。
  也就是爲一個對象在不同的地址空間提供局部代表,這樣可以隱藏一個對象存在於不同地址空間的事實。比如說 WebService,當我們在應用程序的項目中加入一個 Web 引用,引用一個 WebService,此時會在項目中聲稱一個 WebReference 的文件夾和一些文件,這個就是起代理作用的,這樣可以讓那個客戶端程序調用代理解決遠程訪問的問題;

3.2、虛擬代理。
  是根據需要創建開銷很大的對象,通過它來存放實例化需要很長時間的真實對象。這樣就可以達到性能的最優化,比如打開一個網頁,這個網頁裏面包含了大量的文字和圖片,但我們可以很快看到文字,但是圖片卻是一張一張地下載後才能看到,那些未打開的圖片框,就是通過虛擬代裏來替換了真實的圖片,此時代理存儲了真實圖片的路徑和尺寸;

3.3、安全代理。
  用來控制真實對象訪問時的權限。一般用於對象應該有不同的訪問權限的時候;

3.4、指針引用。
  是指當調用真實的對象時,代理處理另外一些事。比如計算真實對象的引用次數,這樣當該對象沒有引用時,可以自動釋放它,或當第一次引用一個持久對象時,將它裝入內存,或是在訪問一個實際對象前,檢查是否已經釋放它,以確保其他對象不能改變它。這些都是通過代理在訪問一個對象時附加一些內務處理;

3.5、延遲加載。
  用代理模式實現延遲加載的一個經典應用就在 Hibernate 框架裏面。當 Hibernate 加載實體 bean 時,並不會一次性將數據庫所有的數據都裝載。默認情況下,它會採取延遲加載的機制,以提高系統的性能。Hibernate 中的延遲加載主要分爲屬性的延遲加載和關聯表的延時加載兩類。實現原理是使用代理攔截原有的 getter 方法,在真正使用對象數據時纔去數據庫或者其他第三方組件加載實際的數據,從而提升系統性能。

  設計模式是前人工作的總結和提煉。通常,被人們廣泛流傳的設計模式都是對某一特定問題的成熟的解決方案。如果能合理地使用設計模式,不僅能使系統更容易地被他人理解,同時也能使系統擁有更加合理的結構。本文對代理模式的 4 種角色、延遲加載、動態代理等做了一些介紹,希望能夠幫助讀者對代理模式有進一步的瞭解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章