1. 程式人生 > >如何手寫JDK動態代理

如何手寫JDK動態代理

1、關於靜態代理和動態代理的區別

靜態代理:

由程式設計師事先生成原始碼再對其編譯。在程式執行前,代理類的.class檔案就已經存在了。

動態代理類:

在程式執行時,運用 Java 反射機制動態建立而成。

比較:

  1. 靜態代理通常只代理一個類,動態代理是代理一個介面下的多個實現類。
  2. 靜態代理事先知道要代理的是什麼,而動態代理不知道要代理什麼東西,只有在執行時才知道。
  3. 動態代理是實現JDK裡的InvocationHandler介面的invoke方法,但注意的是代理的是介面,也就是你的業務類必須要實現介面,通過Proxy裡的newProxyInstance得到代理物件。
  4. 還有一種動態代理CGLIB,代理的是類,不需要業務類繼承介面,通過派生的子類來實現代理。通過在執行時,動態修改位元組碼達到修改類的目的。
2、JDK 動態代理的實現
  1. Person.java 介面類
package com.leitan.architect.pattern.proxy.sta;

/**
 * @Author: tan.lei
 * @Date: 2018-10-17 14:45
 */
public interface Person {

    void findPartner();

    void findHouse();

    void findJob();

    void shopping();

}

  1. Xiaoming.java 被代理類
package com.leitan.architect.
pattern.proxy.jdk; import com.leitan.architect.pattern.proxy.sta.Person; /** * @Author: tan.lei * @Date: 2018-10-17 14:52 */ public class Xiaoming implements Person { @Override public void findPartner() { System.out.println("需要膚白、貌美、大長腿的伴侶...."); } @Override public void
findHouse() { System.out.println("需要環境安靜的房子"); } @Override public void findJob() { System.out.println("需要高薪的工作"); } @Override public void shopping() { System.out.println("需要160m²的海景別墅"); } }
  1. MyInvocationHandler.java 處理介面,類似參考 JDK 的 InvocationHandler
package com.leitan.architect.pattern.proxy.mine;

import java.lang.reflect.Method;

/**
 * @Author: tan.lei
 * @Date: 2018-10-17 16:49
 */
public interface MyInvocationHandler {

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

  1. MyMeipo.java 媒婆的實現類
package com.leitan.architect.pattern.proxy.mine;

import com.leitan.architect.pattern.proxy.sta.Person;

import java.lang.reflect.Method;

/**
 * @Author: tan.lei
 * @Date: 2018-10-18 10:09
 */
public class MyMeipo implements MyInvocationHandler {


    private Person target;// 被代理的物件,把引用儲存下來


    public Object getInstance(Person target) throws Exception {
        this.target = target;

        Class<?> clazz = target.getClass();

        // 用來生成一個新物件(位元組碼重組來實現)
        return MyProxy.newProxyInstance(new MyClassLoader(), clazz.getInterfaces(), this);

    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("請給我你的需求,開始幫你物色...");

        method.invoke(this.target, args);// 目標類的方法呼叫,需要找媳婦的條件

        System.out.println("如果找到符合要求的就安排給你!");

        return null;
    }
}

根據媒婆的要求,目前還需要

MyProxy.java 需要動態生成的代理類

MyClassLoader.java 自定義的類載入器

  1. MyProxy.java 代理類
package com.leitan.architect.pattern.proxy.mine;

import javax.tools.JavaCompiler;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;
import java.io.File;
import java.io.FileWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

/**
 * @Author: tan.lei
 * @Date: 2018-10-17 16:48
 */
public class MyProxy {

    private static final String LN = "\r\n";// 換行


    /**
     * 獲取動態代理類
     *
     * @param classLoader 自定義的類載入器
     * @param interfaces  被代理的目標介面
     * @param handler     實際的業務處理實現
     * @return
     */
    public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler handler) {

        try {
            // 1.動態生成原始碼.java檔案
            String src = generateSrc(interfaces);

            // 2.Java檔案輸出到磁碟
            String filePath = MyProxy.class.getResource("").getPath();
            System.out.println("FilePath = " + 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 manager = compiler.getStandardFileManager(null, null, null);
            Iterable iterable = manager.getJavaFileObjects(f);
            JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
            task.call();
            manager.close();

            // 4.把.class載入到JVM中
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor constructor = proxyClass.getConstructor(MyInvocationHandler.class);
            f.delete();// 編譯完成首刪除.java原檔案

            // 5.返回位元組碼重組後新生成的物件
            return constructor.newInstance(handler);

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

        return null;
    }

    /**
     * 拼接需要動態生成代理類的字串內容
     *
     * @param interfaces 被代理類的介面
     * @return
     */
    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append("package com.leitan.architect.pattern.proxy.mine;" + LN);
        sb.append(LN);

        sb.append("import " + interfaces[0].getName() + ";" + LN);
        sb.append("import java.lang.reflect.Method;" + LN);
        sb.append(LN);

        sb.append("public class $Proxy0 implements " + interfaces[0].getSimpleName() + " {" + LN);
        sb.append(LN);

        sb.append("public MyInvocationHandler handler;" + LN);
        sb.append(LN);

        sb.append("public $Proxy0(MyInvocationHandler handler) {" + LN);
        sb.append("this.handler = handler;" + LN);
        sb.append("}" + LN);
        sb.append(LN);

        for (Method method : interfaces[0].getMethods()) {
            sb.append("public " + method.getReturnType().getName() + " " + method.getName() + "() {" + LN);
            sb.append("try {" + LN);
            sb.append("Method m = " + interfaces[0].getSimpleName() + ".class.getMethod(\"" + method.getName() + "\", new Class[]{});" + LN);
            sb.append("this.handler.invoke(this, m, null);" + LN);
            sb.append("} catch(Throwable e) { " + LN);
            sb.append("e.printStackTrace();" + LN);
            sb.append("}" + LN);
            sb.append("}" + LN);
            sb.append(LN);
        }

        sb.append("}" + LN);

        return sb.toString();
    }

}

  1. MyClassLoader.java 自定義類載入器
package com.leitan.architect.pattern.proxy.mine;

import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @Author: tan.lei
 * @Date: 2018-10-17 16:50
 */
public class MyClassLoader extends ClassLoader {

    private File classPathFile;// class的存放路徑

    public MyClassLoader() {
        String classPath = MyClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class<?> resClass = null;

        if (classPathFile != null) {
            File classFile = new File(classPathFile, name.replaceAll("\\.", "/") + ".class");
            if (classFile.exists()) {
                FileInputStream in = null;
                ByteOutputStream out = null;// 把class轉換成位元組碼寫到JVM中
                try {
                    in = new FileInputStream(classFile);
                    out = new ByteOutputStream();
                    byte[] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1) out.write(buff, 0, len);

                    String className = MyClassLoader.class.getPackage().getName() + "." + name;
                    resClass = defineClass(className, out.getBytes(), 0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        if (in != null) in.close();
                        if (out != null) out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }

        return resClass;
    }
}

  1. MyProxyTest.java 測試類

    package com.leitan.architect.pattern.proxy.mine;
    
    import com.leitan.architect.pattern.proxy.jdk.Xiaoming;
    import com.leitan.architect.pattern.proxy.sta.Person;
    
    /**
     * @Author: tan.lei
     * @Date: 2018-10-18 10:07
     */
    public class MyProxyTest {
    
    
        public static void main(String[] args) {
            try {
                Person obj = (Person) new MyMeipo().getInstance(new Xiaoming());
                System.out.println(obj.getClass());// 這個類是位元組碼動態生成的
                // 你在這裡看似呼叫目標類的方法,實際上是呼叫動態生成的代理類方法,目標類的方法已經經過了動態類的邏輯包裝
                obj.findPartner();
                //obj.findHouse();
                //obj.findJob();
                //obj.shopping();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    
    

    測試結果如下:

    FilePath = /Users/leitan/Worker/IdeaSpace/architect/target/classes/com/leitan/architect/pattern/proxy/mine/ class com.leitan.architect.pattern.proxy.mine.$Proxy0 媒婆:請給我你的需求,開始幫你物色… 需要膚白、貌美、大長腿的伴侶… 如果找到符合要求的就安排給你!

    Process finished with exit code 0

  2. 根據上面的介面已經實現了找媳婦的動態代理