如何手写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

  1. 根据上面的接口已经实现了找媳妇的动态代理,这里再把动态生成的代理类的源码贴出一下,能够看得更加清楚明白
package com.leitan.architect.pattern.proxy.mine;

import com.leitan.architect.pattern.proxy.sta.Person;
import java.lang.reflect.Method;

public class $Proxy0 implements Person {
    public MyInvocationHandler handler;

    public $Proxy0(MyInvocationHandler handler) {
        this.handler = handler;
    }

    public void findHouse() {
        try {
            Method m = Person.class.getMethod("findHouse", new Class[]{});
            this.handler.invoke(this, m, null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public void shopping() {
        try {
            Method m = Person.class.getMethod("shoping", new Class[]{});
            this.handler.invoke(this, m, null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public void findJob() {
        try {
            Method m = Person.class.getMethod("findJob", new Class[]{});
            this.handler.invoke(this, m, null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public void findPartner() {
        try {
            Method m = Person.class.getMethod("findPartner", new Class[]{});
            this.handler.invoke(this, m, null);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值