手写JDK动态代理

在手写JDK动态代理实现之前,我们先看一下我们平时是怎么用的。

举个例子,你想找对象,但是没有时间,只能委托媒婆去帮你找,代码实现如下:

// 一个统一接口
public interface Person {
    // 找对象接口
     void findLove();
}
// 客户类
public class Customer implements Person{

    @Override
    public void findLove() {
        System.out.println("肤白貌美大长腿");
    }
}
package jdkProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
// 媒婆类
public class JDKMeipo implements InvocationHandler {
    // 被代理的对象,把引用保存下来.
    private Object target;

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

        Class<?> clazz = target.getClass();
        return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        method.invoke(this.target, args);
        after();
        return null;
    }

    private void before(){
        System.out.println("媒婆出征,确认需求");
        System.out.println("开始物色");
    }
    private void after(){
        System.out.println("物色结束,开始办事");
    }
}
package proxy;

import jdkProxy.JDKMeipo;

public class Test {
    public static void main(String[] args) {
        try{
           
            Person obj = (Person)new JDKMeipo().getInstance(new Customer());
            System.out.println(obj.getClass());
            obj.findLove();
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
}

运行结果:

大家可以把这四个方法复制到idea中跑一下就知道了,在执行我们想要执行的findLove方法时,会去执行JDKMeipo的invoke方法,然后在里面加一些其他操作,再由塔来调用我们真正要执行的方法。在这里面我没有加过多注释,因为我们接下来会自己手写一个,一步步来捋逻辑。

public static void main(String[] args) {
    try{
        // 1:千里之行,始于足下,从此开始。
        Person obj = (Person) new GPMeipo().getInstance(new Customer());
        System.out.println(obj.getClass());
        obj.findLove();
    }catch (Exception e) {
        e.printStackTrace();
    }
}

在1中,我们用了GPMeipo类的getInstance方法传入被代理的类,返回代理类,我们看看这个类的逻辑

package proxy;

import java.lang.reflect.Method;

public class GPMeipo implements GPInvocationHandler{
    // 被代理的对象,把引用保存下来
    private Object target;

    /**
     * 2:返回代理类
     * @param target 被代理的类
     * @return 代理类
     * @throws Exception
     */
    public Object getInstance(Object target) throws Exception{
        this.target = target;

        Class<?> clazz = target.getClass();
        return GPProxy.newProxyInstance(new GPClassLoader(), clazz.getInterfaces(),this);
    }

    /**
     * 6:通过我们动态生成的java文件,我们所有被代理的方法,在被执行时候都会先来执行这个invoke方法
     * @param proxy 动态拼接的.java代理类
     * @param method 被代理的方法
     * @param args 被代理的方法的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        before();
        method.invoke(this.target, args);
        after();
        return null;
    }

    private void before(){
        System.out.println("媒婆出征,确认需求");
        System.out.println("开始物色");
    }
    private void after(){
        System.out.println("物色结束,开始办事");
    }
}

其中GPInvocationHandler只是一个接口

package proxy;

import java.lang.reflect.Method;

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

在getInstance方法里面,我们又用到了GPProxy.newProxyInstance,传入了class加载器,代理类的方法,被代理类。我们先看看类加载器是什么东西:

package proxy;

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

public class GPClassLoader extends ClassLoader{
    private File classPathFile;

    /**
     * 3:初始化GPClassLoader,先只是将GPClassLoader的class文件加载进来
     */
    public GPClassLoader() {
        String classPath = GPClassLoader.class.getResource("").getPath();
        this.classPathFile = new File(classPath);
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = GPClassLoader.class.getPackage().getName() + "." + name;

        if(classPathFile != null) {
            File classFile = new File(classPathFile, name.replaceAll("\\.","/") + ".class");
            if(classFile.exists()) {
                FileInputStream in = null;
                ByteArrayOutputStream out = null;

                try{
                    in = new FileInputStream(classFile);
                    out = new ByteArrayOutputStream();
                    byte [] buff = new byte[1024];
                    int len;
                    while ((len = in.read(buff)) != -1) {
                        out.write(buff,0,len);
                    }
                    return defineClass(className,out.toByteArray(),0, out.size());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if(null != in) {
                        try{
                            in.close();
                        }catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                    if(null != out) {
                        try{
                            out.close();
                        }catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }

        return null;
    }
}

很明显,我们在new GPClassLoader()时,只是对其属性classPathFile赋上了它这个类的class文件,为后面的findClass方法提供一个地址。

重点还是GPProxy.newProxyInstance,让我们看看这个类的逻辑:

package proxy;

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;
import java.nio.file.attribute.AclFileAttributeView;
import java.util.HashMap;
import java.util.Map;

public class GPProxy {
    public static final String ln = "\r\n";

    /**
     * 4:获取代理类
     * @param classLoader class加载类
     * @param interfaces 代理类要被代理的方法
     * @param h 调用处理类
     * @return
     */
    public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces, GPInvocationHandler h) {
        try{
            // 动态生成源代码.java文件
            String src = generateSrc(interfaces);

            // Java文件输出磁盘
            String filePath = GPProxy.class.getResource("").getPath();
            File f = new File(filePath + "$Proxy0.java");
            FileWriter fw = new FileWriter(f);
            fw.write(src);
            fw.flush();
            fw.close();
            // 把生成的.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();

            // 把编译生成的.class文件加载到JVM中
            Class proxyClass = classLoader.findClass("$Proxy0");
            Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
            f.delete();

            // 返回字节码重组以后的新的代理对象,c是我们动态生成的类的构造方法,h就是外面的媒婆类
            return c.newInstance(h);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 动态拼接.java文件
     * @param interfaces 代理类要被代理的方法
     * @return 返回.java文件
     */
    private static String generateSrc(Class<?>[] interfaces) {
        StringBuffer sb = new StringBuffer();
        sb.append("package proxy;" + ln);
        sb.append("import proxy.Person;" + ln);
        sb.append("import java.lang.reflect.*;" + ln);
        sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
           sb.append("GPInvocationHandler h;" + ln);
           sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
              sb.append("this.h = h;");
           sb.append("}" + ln);
           // 这里就是将Person类的所有方法都遍历了
           for(Method m : interfaces[0].getMethods()) {
               // 方法的参数
               Class<?>[] params = m.getParameterTypes();

               StringBuffer paramsNames = new StringBuffer();
               StringBuffer paramValues = new StringBuffer();
               StringBuffer paramClasses = new StringBuffer();

               for(int i = 0; i < params.length; i++) {
                   Class clazz = params[i];
                   String type = clazz.getName();
                   String paramName = toLowerFirstCase(clazz.getSimpleName());
                   paramsNames.append(type + " " + paramName);
                   paramValues.append(paramName);
                   paramClasses.append(clazz.getName() + ".class");
                   if(i > 0 && i < params.length-1){
                       paramsNames.append(",");
                       paramClasses.append(",");
                       paramValues.append(",");
                   }
               }

               sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramsNames.toString() + ") {" + ln);
                   sb.append("try{" + ln);
                       sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
                       // 5:注意这里将GPInvocationHandler的invoke方法已经加上了,相当于所有执行方法都会去执行invoke了
                       sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues +"})",m.getReturnType()) +";" + ln);
                   sb.append("}catch(Error _ex) { }");
                   sb.append("catch(Throwable e){" + ln);
                   sb.append("throw new UndeclaredThrowableException(e);"+ln);
                   sb.append("}");
                   sb.append(getReturnEmptyCode(m.getReturnType()));
               sb.append("}");
           }
           sb.append("}" + ln);
           return sb.toString();
    }

    private static Map<Class, Class> mappings = new HashMap<Class, Class>();
    static {
        mappings.put(int.class, Integer.class);
    }

    private static String getReturnEmptyCode(Class<?> returnClass) {
        if(mappings.containsKey(returnClass)){
            return "return 0;";
        }else if(returnClass == void.class){
            return "";
        }else {
            return "return null;";
        }
    }

    private static String getCaseCode(String code, Class<?> returnClass) {
        if(mappings.containsKey(returnClass)){
            return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
        }
        return code;
    }

    private static boolean hasReturnValue(Class<?> clazz) {
        return clazz != void.class;
    }

    private static String toLowerFirstCase(String src) {
        char [] chars = src.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }
}

newProxyInstance方法通过动态拼接.java文件,在里面不去调用被代理类的代理方法,而是去调用代理类的invoke方法,注意看数字5的地方标明的注释,然后讲这个java文件编译成class加载到JVM中,直接调换掉了以前的,来了一波偷梁换柱,最后返回代理类。

JDK的动态代理的精髓就是利用传入的参数,动态拼接成我们想要的java文件,编译成class,加载到JVM,进行偷梁换柱,大家可能单纯的看不会太理解,建议大家如果想吃透可以把代码复制下来,按照我的数字注释顺序,自己debug一下,看看参数,应该就会理解JDK动态代理的乾坤了。

大家一定注意动态拼接java文件时,拼接的包的位置,可以参考我的包路径,如图:

本文参考:《Spring5核心原理与30个类手写实战》,自己加了一些注释帮助理解,如有侵权,联系秒删。

有疑问可以在评论区交流,作者看见会回复。

如需转载,请一定声明原处。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值