手寫動態代理--模仿jdk動態代理

爲了便於理解jdk動態代理,模仿jdk動態代理的思路,模擬寫一個動態代理demo,V1思路如下:

  • 拼接代理對象源碼,使用文件流寫出$Proxy.java文件
  • 將$Proxy.java動態編譯成.class文件
  • $Proxy.class文件動態加載到JVM內存中,產生Class對象
  • 使用反射構造出代理對象
  • 用戶調用代理對象實現方法代理

源碼如下:

定義接口

package com.ant.myJdkProxy;

/**
 * 接口,模擬有無返回值、有無參數多種情況方法
 */
public interface IndexDao {
    void test();
    void test(String s);
    String testReturn(String s,Integer i);
}

定義實現類,即被代理對象:

package com.ant.myJdkProxy;

/**
 * 接口實現類
 */
public class IndexDaoImpl implements IndexDao{
    @Override
    public void test() {
        System.out.println("test()");
    }

    @Override
    public void test(String s) {
        System.out.println("Test("+s+")");
    }

    @Override
    public String testReturn(String s, Integer i) {
        System.out.println("testReturn("+s+","+i);
        return "hshsh";
    }
}

定義產生代理的工具類:

package com.ant.myJdkProxy;

import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 代理工具類
 * 手動模擬jdk動態代理
 * 步驟:根據傳進來的目標代理對象,動態實現代理邏輯的代理java對象文件
 * .java文件-->編譯成class文件-->加載至jvm虛擬機內存,稱爲class對象-->反射創建代理對象-->調用代理方法實現代理
 *
 */
public class ProxyUtil {

    /**
     * 生成java文件,假設生成在D盤下,包名是com.ant
     * @param target 需要被代理的目標對象
     * @param interfc 目標對象實現的接口
     * @return
     */
    public static Object getProxy(Object target,Class interfc) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if(null == target || null == interfc){
            throw new IllegalArgumentException("參數不允許爲空");
        }
        if(!interfc.isInterface()){
            throw new IllegalArgumentException("必須是接口");
        }
        //1.生成java源代碼字符串
        StringBuilder sb = genJavaStr(target,interfc);
        //2.將源代碼寫成$Proxy.java至D:/com/ant下
        File dir = new File("D:\\com\\ant");
        if(!dir.exists()){
            dir.mkdirs();
        }
        File file = new File("D:\\com\\ant\\$Proxy.java");
        if(!file.exists()){
            file.createNewFile();
        }

        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(sb.toString());
        fileWriter.flush();
        fileWriter.close();

        //3.將$Proxy.java動態編譯成字節碼
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(file);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();

        //4.將class文件動態加載到jvm虛擬機內存中
        URL[] urls = new URL[]{new URL("file:D:\\\\")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class<?> clazz = urlClassLoader.loadClass("com.ant.$Proxy");

        //5.反射創建代理對象
        Constructor<?> constructor = clazz.getConstructor(target.getClass());
        return constructor.newInstance(target);
    }

    /**
     * 生成源代碼字符串
     * package com.ant
     * public class $Proxy implement interface{
     *     private Object target;
     *
     *     public $Proxy (Object target){
     *         this.target = target;
     *     }
     *
     *     //重寫各個interface中的方法,並且在前後加上邏輯,執行目標對象的方法
     *      @Override
     *      public returnType methosName(args){
     *          System.out.println("before")
     *          returntype result = target.methosName(args);
                System.out.println("after");
     *          return result;
     *      }
     * }
     */

    private static StringBuilder genJavaStr(Object target,Class interfc) {
        String line = "\r\n";
        String tab = "\t";
        StringBuilder sb = new StringBuilder();
        sb
                .append("package com.ant;")
                .append(line)
                .append("public class $Proxy implements ").append(interfc.getName()).append(" {")
                .append(line)
                .append(tab)
                .append("private ").append(target.getClass().getName()).append(" target;").append(line)
                .append(tab).append("public $Proxy(").append(target.getClass().getName()).append(" target").append("){").append(line)
                .append(tab).append(tab).append("this.target = target;").append(line)
                .append(tab).append("}")
                .append(line);
        //方法
        Method[] methods = interfc.getMethods();
        if(null!=methods && methods.length>0){
            for(Method m:methods){
                sb.append(line).append(tab)
                        .append("@Override").append(line)
                        .append(tab)
                        //方法名稱
                        .append("public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(");
                Class<?>[] parameterTypes = m.getParameterTypes();
                //增加方法參數
                String modalityParam = "";
                if(null!=parameterTypes && parameterTypes.length>0){
                    StringBuilder sbu = new StringBuilder();
                    //var1,var2,var3  目標對象方法調用時使用
                    StringBuilder modalitySbu = new StringBuilder();
                    for(int i=0;i<parameterTypes.length;i++){
                        sbu.append(" ").append(parameterTypes[i].getName()).append(" var"+i).append(",");
                        modalitySbu.append("var"+i).append(",");
                    }
                    String temp = sbu.toString();
                    sb.append(temp.substring(0,sbu.lastIndexOf(",")));
                    String tempPa = modalitySbu.toString();
                    modalityParam = tempPa.substring(0,tempPa.lastIndexOf(","));
                }
                sb.append("){").append(line)
                        .append(tab).append(tab)
                        .append("System.out.println(\"方法邏輯處理之前\")").append(";").append(line)
                        .append(tab).append(tab);
                        if(void.class!=m.getReturnType()){
                            sb.append("return ");
                        }
                sb.append("target.").append(m.getName()).append("(").append(modalityParam).append(");").append(line)
                        .append(tab).append("}");
            }
        }
        sb.append(line).append("}");
        return sb;
    }
}

測試主函數:

package com.ant.myJdkProxy;

/**
 * 測試主函數
 */
public class Main {
    public static void main(String[] args){
        IndexDao indexDao = new IndexDaoImpl();
        try {
            IndexDao proxy = (IndexDao)ProxyUtil.getProxy(indexDao, IndexDao.class);
            proxy.test();
            proxy.test("success");
            proxy.testReturn("success",2333);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

到目前爲止我們簡單的動態代理已經寫完,但是V1版本存在的問題:代理邏輯是我們寫死的,無法達到用戶自行控制代理邏輯,即用戶無法通過先執行類似於加鎖,執行被代理邏輯,再解鎖這種方式控制,總得來說代理邏輯比較死,因此我們繼續向下升級我們的動態代理邏輯,思路如下:我們通過定義類似於jdk的InvocationHandler方式定義一個我們自己的接口MyInvocationHandler,當用戶執行代理對象的方法時,實際上是執行MyInvocationHandler.invoke方法,在invoke方法,我們將目標對象,目標對象執行的Method對象以及方法參數傳給用戶,由用戶自行調用Method.invoke()方法,同時用戶可以在調用invoke方法之前和之後增加自己的自定義邏輯,來實現完全與jdk動態代理相同的靈活性:

目標接口:

package com.ant.myJdkProxy.v2;

/**
 * 接口,模擬有無返回值、有無參數多種情況方法
 */
public interface IndexDao {
    void test() throws Throwable;
    void test(String s);
    String testReturn(String s, Integer i);
}

接口實現類:

package com.ant.myJdkProxy.v2;

/**
 * 接口實現類
 */
public class IndexDaoImpl implements IndexDao {
    @Override
    public void test() {
        System.out.println("test()");
    }

    @Override
    public void test(String s) {
        System.out.println("Test("+s+")");
    }

    @Override
    public String testReturn(String s, Integer i) {
        System.out.println("testReturn("+s+","+i);
        return "hshsh";
    }
}

自定義的InvocationHandler接口:

package com.ant.myJdkProxy.v2;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public interface MyInvocationHandler {

    /**
     * 模擬jdk InvocationHandler,實際上爲了能夠讓用戶自定義代理邏輯,提供給用戶的鉤子函數,由用戶定義代理邏輯與真實方法調用的順序
     * 當用戶獲取到代理對象調用方法時,代理對象方法中實際上是調用本方法
     * @param method
     * @param args
     * @return
     */
    Object invoke(Object target,Method method,Object...args) throws InvocationTargetException, IllegalAccessException;

}

用戶使用代理時,自行創建的InvocationHandler實現,用於實現代理邏輯:

package com.ant.myJdkProxy.v2;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class IndexInvocationHandler implements MyInvocationHandler {
    @Override
    public Object invoke(Object target, Method method, Object... args) throws InvocationTargetException, IllegalAccessException {
        System.out.println("方法執行開始前");
        Object o = method.invoke(target, args);
        System.out.println("方法執行開始後");
        return o;

    }
}

代理工具類:

package com.ant.myJdkProxy.v2;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * 代理工具類
 * 手動模擬jdk動態代理
 * 步驟:根據傳進來的目標代理對象,動態實現代理邏輯的代理java對象文件
 * .java文件-->編譯成class文件-->加載至jvm虛擬機內存,稱爲class對象-->反射創建代理對象-->調用代理方法實現代理
 */
public class ProxyUtil {

    /**
     * 生成java文件,假設生成在D盤下,包名是com.ant
     *
     * @param target  需要被代理的目標對象
     * @param interfc 目標對象實現的接口
     * @return
     */
    public static Object getProxy(Object target, Class interfc, MyInvocationHandler invocationHandler) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        if (null == target || null == interfc) {
            throw new IllegalArgumentException("參數不允許爲空");
        }
        if (!interfc.isInterface()) {
            throw new IllegalArgumentException("必須是接口");
        }
        //1.生成java源代碼字符串
        StringBuilder sb = genJavaStr(target, interfc, invocationHandler);
        //2.將源代碼寫成$Proxy.java至D:/com/ant下
        File file = createJavaFile(sb.toString());
        //3.將$Proxy.java動態編譯成字節碼
        compile(file);
        //4.將class文件動態加載到jvm虛擬機內存中
        Class clazz = loadClass();
        //5.反射創建代理對象
        Constructor<?> constructor = clazz.getConstructor(target.getClass(), MyInvocationHandler.class);
        return constructor.newInstance(target,invocationHandler);
    }

    /**
     * 生成源代碼字符串
     * package com.ant
     * public class $Proxy implement interface{
     * private Object target;
     * <p>
     * public $Proxy (Object target){
     * this.target = target;
     * }
     * <p>
     * //重寫各個interface中的方法,並且在前後加上邏輯,執行目標對象的方法
     *
     * @Override public returnType methosName(args){
     * System.out.println("before")
     * returntype result = target.methosName(args);
     * System.out.println("after");
     * return result;
     * }
     * }
     */

    private static StringBuilder genJavaStr(Object target, Class interfc, MyInvocationHandler invocationHandler) {
        String line = "\r\n";
        String tab = "\t";
        StringBuilder sb = new StringBuilder();
        sb
                .append("package com.ant;")
                .append(line)
                .append("public class $Proxy implements ").append(interfc.getName()).append(" {")
                .append(line)
                .append(tab)
                .append("private ").append(target.getClass().getName()).append(" target;").append(line)
                .append("private ").append("com.ant.myJdkProxy.v2.MyInvocationHandler invocationHandler;").append(line)
                .append(tab).append("public $Proxy(").append(target.getClass().getName()).append(" target").append(",").append("com.ant.myJdkProxy.v2.MyInvocationHandler invocationHandler").append("){").append(line)
                .append(tab).append(tab).append("this.target = target;").append(line)
                .append(tab).append(tab).append("this.invocationHandler = invocationHandler;").append(line)
                .append(tab).append("}")
                .append(line);
        //方法
        Method[] methods = interfc.getMethods();
        if (null != methods && methods.length > 0) {
            for (Method m : methods) {
                sb.append(line).append(tab)
                        .append("@Override").append(line)
                        .append(tab)
                        //方法名稱
                        .append("public ").append(m.getReturnType().getName()).append(" ").append(m.getName()).append("(");
                Class<?>[] parameterTypes = m.getParameterTypes();
                //增加方法參數
                String modalityParam = "";
                String reflectParam = "";
                if (null != parameterTypes && parameterTypes.length > 0) {
                    StringBuilder sbu = new StringBuilder();
                    //var1,var2,var3  目標對象方法調用時使用
                    StringBuilder modalitySbu = new StringBuilder();
                    for (int i = 0; i < parameterTypes.length; i++) {
                        sbu.append(" ").append(parameterTypes[i].getName()).append(" var" + i).append(",");
                        modalitySbu.append("var" + i).append(",");
                        reflectParam = reflectParam + parameterTypes[i].getName() + ".class";
                        reflectParam += ",";
                    }
                    String temp = sbu.toString();
                    sb.append(temp.substring(0, sbu.lastIndexOf(",")));
                    String tempPa = modalitySbu.toString();
                    modalityParam = tempPa.substring(0, tempPa.lastIndexOf(","));
                    reflectParam = reflectParam.substring(0, reflectParam.lastIndexOf(","));
                }
                sb.append("){").append(line)
                        .append(tab).append(tab);
                //方法體
                /*
                try{
                    return (java.lang.String)invocationHandler.invoke(target,target.getDeclaredMethod("test",java.lang.String.class,java.lang.Integer.class),var0,var1,var2);
                }cache(Throwable t){
                    t.printStackTrace();
                }
                    return null;
                 */
                sb.append("try {").append(line).append(tab).append(tab).append(tab);
                if (void.class != m.getReturnType()) {
                    sb.append("return ").append("(").append(m.getReturnType().getName()).append(")");
                }
                sb.append("invocationHandler.invoke(").append("target,")
                        .append("target.getClass().getDeclaredMethod(").append("\"").append(m.getName()).append("\"");
                if (null != reflectParam && reflectParam != "") {
                    sb.append(",").append(reflectParam);
                }
                sb.append(")");
                if (m.getParameterTypes() != null && m.getParameterTypes().length > 0) {
                    sb.append(",").append(modalityParam);
                }
                sb.append(");");
                sb.append(line).append(tab).append("}catch(Throwable t){").append(line)
                        .append(tab).append(tab).append(tab).append("t.printStackTrace();").append(line)
                        .append(tab).append(tab).append("}").append(line);
                if(void.class != m.getReturnType()){
                    sb.append(tab).append(tab).append("return null;");
                }
                sb.append(line).append(tab).append("}");
            }
        }
        sb.append(line).append("}");
        return sb;
    }

    /**
     * 根據源代碼,創建java文件
     *
     * @param sourceCode
     * @return
     * @throws IOException
     */
    private static File createJavaFile(String sourceCode) throws IOException {
        File dir = new File("D:\\com\\ant");
        if (!dir.exists()) {
            dir.mkdirs();
        }
        File file = new File("D:\\com\\ant\\$Proxy.java");
        if (!file.exists()) {
            file.createNewFile();
        }

        FileWriter fileWriter = new FileWriter(file);
        fileWriter.write(sourceCode);
        fileWriter.flush();
        fileWriter.close();
        return file;
    }

    /**
     * 將源代碼編譯成字節碼文件
     *
     * @param file
     * @throws IOException
     */
    private static void compile(File file) throws IOException {
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
        Iterable units = fileMgr.getJavaFileObjects(file);
        JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
        t.call();
        fileMgr.close();
    }

    /**
     * 動態加載class文件到jvm內存中
     *
     * @return
     */
    private static Class loadClass() throws MalformedURLException, ClassNotFoundException {
        URL[] urls = new URL[]{new URL("file:D:\\\\")};
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class<?> clazz = urlClassLoader.loadClass("com.ant.$Proxy");
        return clazz;
    }
}

測試類:

package com.ant.myJdkProxy.v2;

/**
 * 測試主函數
 */
public class Main {
    public static void main(String[] args){
        IndexDao indexDao = new IndexDaoImpl();
        try {
            IndexDao proxy = (IndexDao) ProxyUtil.getProxy(indexDao, IndexDao.class, new IndexInvocationHandler() );
            proxy.test();
            proxy.test("2333");
            proxy.testReturn("牛逼",2333);
        } catch (Exception e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

我們自己實現的jdk動態代理只是demo級別的,有很多內容都沒有考慮到,其次在性能上與遠程的jdk動態代理完全無法比,因爲我們實現的動態代理涉及到多次IO操作,而jdk動態代理直接產生字節碼數組(byte[]),然後直接native方法產生Class對象,通過Class對象反射產生代理對象,速度是我們demo的97倍,測試代碼如下:

package com.ant.myJdkProxy.jdk;

import com.ant.myJdkProxy.v2.IndexDao;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class JdkInvocationHandler implements InvocationHandler {

    private IndexDao indexDao;
    public JdkInvocationHandler(IndexDao indexDao){
        this.indexDao = indexDao;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("jdk before");
        Object result = method.invoke(indexDao,args);
        System.out.println("jdk after");
        return result;

    }
}
package com.ant.myJdkProxy.v2;

import com.ant.myJdkProxy.jdk.JdkInvocationHandler;

import java.lang.reflect.Proxy;

/**
 * 測試主函數
 * 經過測試我們自己寫的動態代理執行時間是jdk動態代理的97倍
 */
public class Main {
    public static void main(String[] args){
        IndexDao indexDao = new IndexDaoImpl();
        try {
            //1973982603
            long start1 = System.nanoTime();
            IndexDao proxy = (IndexDao) ProxyUtil.getProxy(indexDao, IndexDao.class, new IndexInvocationHandler() );
            proxy.test();
            proxy.test("2333");
            proxy.testReturn("牛逼",2333);
            System.out.println(System.nanoTime()-start1);

            //20197073
            long start2 = System.nanoTime();
            IndexDao indexDao1 = (IndexDao) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{IndexDao.class}, new JdkInvocationHandler(new IndexDaoImpl()));
            indexDao1.test();
            indexDao1.test("2333");
            indexDao1.testReturn("牛逼",2333);
            System.out.println(System.nanoTime()-start2);

        } catch (Exception e) {
            e.printStackTrace();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
    }
}

 

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