手寫JDK動態代理(Implememt your own JDK dynamic proxy)

手寫自己的動態代理需要重寫那些內容

首先我們來看看,上一篇文章代理模式(Proxy pattern)的動態代理類中,使用到了那些內容,從中找出需要重新實現的類和方法。
在這裏插入圖片描述

動態生成的 $Proxy0 對象長什麼樣?

把上一篇文章的動態代理的測試類,改造一下,從JDK,保存成文件,我們來看看 $Proxy0 對象長什麼樣。

/**
 * JDK實現的動態代理測試類
 */
public class JdkProxyTest {
    public static void main(String[] args) {
        Customer customer = new Customer("陳大春");

        Object proxyObj = new JdkHouseProxy().getInstance(customer);
        Rent rent = (Rent) proxyObj;

        rent.rent("獨棟別野小洋樓,400平,室內泳池");
        rent.buy(4000000)
        
        //從JDK的代理生成器中,獲取代理類 Proxy0 ,保存到磁盤中
        try {
            byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Rent.class});
            FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
            os.write(bytes);
            os.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

運行程序以後,在D盤下,會生成了一個 $Proxy.class 文件,把改文件拖入IDEA自動反編譯,反編譯後的代碼如下:


import com.lenjor.jdkproxy.Rent;
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 Rent {
    private static Method m1;
    private static Method m3;
    private static Method m4;
    private static Method m2;
    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 buy(int 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 String rent(String var1) throws  {
        try {
            return (String)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 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"));
            m3 = Class.forName("com.lenjor.jdkproxy.Rent").getMethod("buy", Integer.TYPE);
            m4 = Class.forName("com.lenjor.jdkproxy.Rent").getMethod("rent", Class.forName("java.lang.String"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            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.class代碼分析

在這裏插入圖片描述
在這裏插入圖片描述
從上面的兩張圖中,我們就可以知道,$Proxy0 類繼承自Proxy類,是JVM動態生成的類被final修飾。同樣實現了被代理類的接口,當調用代理的方法時,執行的是

 public final String buy(int 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);
        }
    }

請注意這裏的InvokecationHandler,實際上是從哪裏來的?
在這裏插入圖片描述

看到這裏,你是不是豁然開朗,原來代理方法是這樣執行的。那麼下面我們就開始進入本文的重點,自己實現一個JDK動態代理。

實現動態代理的關鍵

關鍵在於獲取動態代理的方法
public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h)
throws IllegalArgumentException{}

1. 動態生成源代碼.java文件
2. Java文件輸出磁盤
3. 把生成的.java文件編譯成.class文件
4. 編譯生成的.class文件加載到JVM中
5. 返回字節碼重組以後的新的代理對象

實現完整代碼

/**
 * 租房,買房
 */
public interface Rent {
    public String rent(String need);
    public int buy(int money);
    public void sale(int money);
}



/**
 * 房客
 */
public class Customer implements Rent{

    private String name;

    public Customer(String name){
        this.name = name;
    }

    @Override
    public String rent(String need) {
        System.out.println(name + "要租的房子的需求是:" + need);
        return name + "'s Tenant.rent()";
    }

    @Override
    public int buy(int money) {
        System.out.println(name + "購買房子出價 :" + money);
//        return name + "'s Tenant.buy()";
        return money;
    }

    @Override
    public void sale(int money) {
        System.out.println(name + "賣出房子出價 :" + money);
    }

}



/**
 * 自定義類加載器
 */
public class MyClassLoader extends ClassLoader {
    private File classPathfile;

    public MyClassLoader() {
        String classpth = MyClassLoader.class.getResource("").getPath();
        classPathfile = new File(classpth);
    }

    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        String className = MyClassLoader.class.getPackage().getName() + "." + name;
        if (classPathfile != null) {
            File file = new File(classPathfile, name + ".class");
            FileInputStream fileInputStream = null;
            ByteArrayOutputStream outputStream = null;
            try {
                fileInputStream = new FileInputStream(file);
                outputStream = new ByteArrayOutputStream();
                byte[] buff = new byte[1024];
                int len;
                while ((len = fileInputStream.read(buff)) != -1) {
                    outputStream.write(buff, 0, len);
                }
                return defineClass(className, outputStream.toByteArray(), 0, outputStream.size());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (null != fileInputStream) {
                    try {
                        fileInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (null != outputStream) {
                    try {
                        outputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return null;
    }
}



/**
 * 自定義InvocationHandler接口
 */
public interface MyInvocationHandler {

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;

}



/**
 * 代理類
 */
public class MyHouseProxy implements MyInvocationHandler{
    // 目標對象
    private Object target;

    /**
     * 綁定關係,也就是關聯到哪個接口(與具體的實現類綁定)的哪些方法將被調用時,執行invoke方法。
     */
    public Object getInstance(Object target) {
        this.target = target;

        Class clazz = target.getClass();
        //使用自己定義的ClassLoader和InvokecationHandler
        return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                System.out.println("arg" + i + "=" + args[i]);
            }
        }
        System.out.println("我是中間商----開始幹活");
        Object invokeResult = method.invoke(target, args);
        System.out.println("中間商幹活完畢,收取差價");
        System.out.println("invokeResult =" + invokeResult + "\n");
        return invokeResult;
    }
}


自定義代理類實現


/**
 * 自定義代理類實現
 */
public class MyProxy {
    public static final String ln = "\r\n";

    /**
     * 獲取代理對象方法
     *
     * @param loader
     * @param interfaces
     * @param h
     * @return
     * @throws IllegalArgumentException
     */
    public static Object newProxyInstance(MyClassLoader loader, Class<?>[] interfaces, MyInvocationHandler h)
            throws IllegalArgumentException {
        try {
            // 1 java源碼
            String src = generateSrc(interfaces);
            // 2 將源碼輸出到java文件中
            String filePath = MyProxy.class.getResource("").getPath();
            System.out.println("生成的.java文件目錄:" + filePath);
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();

            //3、將java文件編譯成class文件
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager manage = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manage.getJavaFileObjects(f);

            JavaCompiler.CompilationTask task = compiler.getTask(null, manage, null, null, null, iterable);
            task.call();
            manage.close();

            //4、將class加載進jvm
            Class proxyClass = loader.findClass("$Proxy0");
            //TODO 刪除動態生成的.java文件
            f.delete();

            //5、返回代理類對象
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            return constructor.newInstance(h);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 生成.Java文件源代碼
     *
     * @param interfaces
     * @return
     */
    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append("package com.lenjor.myproxy;" + ln);
        sb.append("import java.lang.reflect.Method;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
        sb.append("private MyInvocationHandler h;" + ln);
        sb.append("public $Proxy0(MyInvocationHandler h) {" + ln);
        sb.append("this.h = h;" + ln);
        sb.append("}");
        //遍歷接口定義的方法,實現接口所有的方法
        for (Method m : interfaces[0].getMethods()) {
            sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(");
            //方法的參數
            for (int i = 0; i < m.getParameterTypes().length; i++) {
                sb.append(m.getParameterTypes()[i].getName() + " " + m.getParameters()[i].getName());
                if (i != m.getParameterTypes().length - 1) {
                    sb.append(",");
                }
            }
            sb.append(") {" + ln);
            sb.append("try { " + ln);
            sb.append("Method m = " + interfaces[0].getName()
                    + ".class.getMethod(\"" + m.getName()
                    + "\",new Class[]{");
            //方法的參數列表
            for (int i = 0; i < m.getParameterTypes().length; i++) {
                sb.append(m.getParameters()[i].getType().getName() + ".class");
                if (i != m.getParameterTypes().length - 1) {
                    sb.append(",");
                }
            }
            sb.append("});" + ln);
            //是否有返回值
            boolean returnFlag = !m.getReturnType().getSimpleName().equals(Void.TYPE.toString());
            if (returnFlag) {
                sb.append("return (" + m.getReturnType().getSimpleName() + ")");
            }
            sb.append("this.h.invoke(this, m, new Object[]{");
            for (int i = 0; i < m.getParameterTypes().length; i++) {
                sb.append(m.getParameters()[i].getName());
                if (i != m.getParameterTypes().length - 1) {
                    sb.append(",");
                }
            }
            sb.append("});" + ln);
            sb.append("} catch(Throwable e) {" + ln);
            sb.append("e.printStackTrace();" + ln);
            if (returnFlag) {
                switch (m.getReturnType().getSimpleName()) {
                    case "int":
                    case "long":
                    case "short":
                    case "float":
                    case "double":
                        sb.append("return 0;");
                        break;
                    case "boolean":
                        sb.append("return false;");
                        break;
                    case "char":
                    case "byte":
                        sb.append("return ' ';");
                        break;
                    default:
                        sb.append("return null;");
                }
            }
            sb.append("}" + ln);
            sb.append("}" + ln);
        }

        sb.append("}" + ln);
        return sb.toString();
    }
}

測試方法

public class MyProxyTest {
    public static void main(String[] args){
        Customer customer = new Customer("陳大春");

        Object proxyObj = new MyHouseProxy().getInstance(customer);
        Rent rent = (Rent) proxyObj;
        System.out.println("proxyObj = " + proxyObj.getClass());
        rent.rent("獨棟別野小洋樓,400平,室內泳池");
    }
}

運行結果

在這裏插入圖片描述

總結

大功告成,手寫JDK動態代理的過程就完成了。
使用Java代碼動態生成.java代碼文件時,使用StringBuffer拼接代碼的時候容易出錯,建議把生成的文件放到IDEA格式化後,排查語法錯誤。當然JDK的真正生成類的代碼,並不是像我手寫的MyProxy類這樣生成的,有興趣的同學可以自行研究一下JDK的Proxy類源碼。

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