1. 程式人生 > >JDK動態代理深入理解分析並手寫簡易JDK動態代理(上)

JDK動態代理深入理解分析並手寫簡易JDK動態代理(上)

部落格真的是好幾個月沒更了,2019新年第一篇,繼續深入動態代理,前兩篇簡單分析了動態代理的實現原理之後,這次繼續深入瞭解具體的實現方式,並手寫一套簡易的動態代理已加強理解;
先接上一篇程式碼,這裡先上程式碼說話,一個簡單案列,代理找物件和找工作:

JDK動態代理只能代理有介面的類,定義Persion介面

package com.guitu18.study.proxy;
/**
 * @author zhangkuan
 * @email [email protected]
 * @Date 2018/12/31 20:30
 */
public interface Persion {
    
/** * 找物件 * * @param condition 對另一半的要求 * @return 表態 */ String findLove(String condition); /** * 找工作 */ void findWord(); }

 

實現介面的類:

package com.guitu18.study.proxy;
/**
 * @author zhangkuan
 * @email [email protected]
 * @Date 2018/12/31 20:13
 
*/ public class ZhangKuan implements Persion { @Override public String findLove(String condition) { System.out.println(condition); return "葉青我愛你"; } @Override public void findWord() { System.out.println("我想找月薪15-25k的工作"); } }

 

代理類:

package
com.guitu18.study.proxy.jdk; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * @author zhangkuan * @email [email protected] * @Date 2018/12/31 20:13 */ public class JDKProxy { public static Object getProxyInstance(final Object target) { return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法執行前 System.out.println("開始事務..."); // 執行方法 Object invoke = method.invoke(target, args); // 方法執行後 System.out.println("提交/回滾事務..."); return invoke; } }); } }

 

代理測試類:

package com.guitu18.study.proxy.jdk;
import com.guitu18.study.proxy.Persion;
import com.guitu18.study.proxy.ZhangKuan;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
/**
 * @author zhangkuan
 * @email [email protected]
 * @Date 2018/12/31 20:28
 */
public class JDKProxyTest {
    public static void main(String[] args) {
        try {
            Persion persion = (Persion) JDKProxy.getProxyInstance(new ZhangKuan());
            String love = persion.findLove("膚白貌美大長腿");
            System.out.println(love); // 葉青我愛你
            System.out.println(persion.getClass()); // class com.sun.proxy.$Proxy0
            /**
             * 上面生成的代理物件位元組碼 com.sun.proxy.$Proxy0 是在記憶體中的
             * 這裡將其物件寫到檔案中,通過反編譯檢視
             */
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Persion.class});
            FileOutputStream fos = new FileOutputStream("D://$Proxy0.class");
            fos.write(bytes);
            System.out.println("檔案寫入成功");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

到這裡,代理任務已經可以完成看;此處,我們將JDK動態代理生成的代理類,通過流的形式儲存到磁碟中了,現在使用idea自帶反編譯工具檢視:

import com.guitu18.study.proxy.Persion;
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 Persion {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    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 findWord() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String findLove(String var1) throws  {
        try {
            return (String)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");
            m4 = Class.forName("com.guitu18.study.proxy.Persion").getMethod("findWord");
            m3 = Class.forName("com.guitu18.study.proxy.Persion").getMethod("findLove", 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());
        }
    }
}

 

看完這段程式碼是不是恍然大悟的感覺,生成的代理類繼承了Proxy類並實現了我們的介面,並生成了被代理類的全部方法,包括toString、equals等等所有方法; 這裡生成的代理類繼承自Proxy,在Proxy中有這麼一段程式碼:

/**
 * the invocation handler for this proxy instance.
 * @serial
 */
protected InvocationHandler h;

 

這就是為什麼我們在生成代理類的時候,要傳入的一個InvocationHandler的實現,我們所有的代理增強程式碼都寫在invoke()方法中,而最終代理類執行代理也是通過呼叫父類Proxy中的InvocationHandler介面的invoke()方法;
而我們知道Java是單繼承的,所以代理類只能去實現我們的介面以供我們可以將其強轉;
在我們呼叫自身方法時,代理類巧妙的通過呼叫生成的同名方法然後呼叫super.h.invoke()方法,最終就到了我們實現InvocationHandler時所寫的invoke()增強方法;在此時,method.invoke()才真正通過反射呼叫了我們自身所寫的方法;這種極為巧妙無侵入式的方法增強實在是妙,令人歎為觀止;

這裡推薦一篇 勞夫子 的 [ ProxyGenerator生成代理類的位元組碼檔案解析 ];
分析的是ProxyGenerator.generateClassFile()位元組碼生成原理,寫的非常好,值得好好看一下;

下一篇,通過手寫一套簡易的JDK動態代理的實現增強理解;