聊聊JDK动态代理原理

什么是代理

为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。——来自百度百科

通俗地讲,就是我们生活中的中介

什么是动态代理

动态代理是在程序运行时通过反射机制操作字节码,从而动态的创建 字节码文件,进而创建代理对象。

不多说了,先上代码

  • 创建接口:
public interface OutInfo {
    void printInfo();
}
  • 创建实现类:
public class Person implements OutInfo {
    private String name;
    private Integer age;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public void printInfo() {
        System.out.println(name + "..." + age);
    }
}
  • 编写动态代理demo
import java.lang.reflect.Proxy;

public class DynamicProxy {
    public static void main(String[] args) {
        Person person = new Person("张三", 23);
        OutInfo oi = (OutInfo) Proxy.newProxyInstance(
                Person.class.getClassLoader(), 
                Person.class.getInterfaces(),
                /*
                 * 此代码基于JDK8编写,所以用到了Lambda表达式
                 * proxy指代理对象, method指当前方法, arg指方法传入的参数
                 */
                (proxy, method, arg) -> {
                    System.out.println("方法前处理");
                    /*
                     * 注意:invoke()的第一个参数只能写被代理对象的实例
                     *  不能写参数proxy,否则会栈溢出,原因后面讲解
                     */
                    Object ret = method.invoke(person, arg);
                    System.out.println("方法后处理");
                    return ret;
                }
        );
        oi.printInfo();
    }
}

看运行结果:
运行结果

从运行结果看,代理结果是没问题的。那么,代理的过程是什么样的呢?

代理过程分析

之前说过动态代理是通过操作字节码来动态的创建代理对象的,那我们先看一下它生成的字节码文件里面有些什么东西:

通过以下代码可以获取到动态代理生成的字节码文件,但是需要独立的JRE才可以,嗯!推荐用JDK8!!!

public void saveProxyClassFile(){
    // 参数:(生成的字节吗对象的名字,被代理对象实现的接口数组)
    byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy", new Class[]{OutInfo.class});
    try(
            FileOutputStream fos =new FileOutputStream(new File("$Proxy.class"))
    ){
        fos.write(bytes);
        fos.flush();
    }catch (Exception e) {
        e.printStackTrace();
    }
}

这是通过idea自带的反编译工具翻译出来的内容:

public final class $Proxy extends Proxy implements OutInfo {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;

    public $Proxy(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 void printInfo() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    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("ann.study.OutInfo").getMethod("printInfo");
            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());
        }
    }
}

通过阅读翻译出来的代码,我们可以知道变量m3保存的是接口的方法对象,而生成的代理方法printInfo()中有这么一行代码:

super.h.invoke(this, m3, (Object[])null);

其中,h就是我们用Lambda表达式传入的匿名对象,而这个方法的作用就是调用我们重写的invoke()方法。

其实这个方法是这样子的:

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

把参数对应起来后,我们就会知道参数proxy的作用,以及动态代理demo中method.invoke(person, arg)中写proxy会引起栈溢出的原因:

> 递归调用引起了死循环,程序无法结束循环,所以会报栈溢出异常

通过这种机制,JDK便完成了动态代理

总结一下

  1. 动态代理是通过操作字节码文件来生成代理对象的,这样就解决了静态代理生成的代理类很多以及维护困难的问题
  2. 动态代理中有一些坑,比如说:
    1. JDK的动态代理要求被代理类必须实现接口,返回的代理对象也是该接口的实现类;如果想不实现接口,可以使用cglib
    2. 在实现InvocationHandler接口,重写incvoke()方法时,要注意method.invoke()的第一个参数要传被代理对象,不能传重写的invoke()方法的第一个参数,因为那个参数是代理对象。

这是博主第一次写博文,内容中错误的地方还请评论指正,同时也希望大家多多包涵。最后,感谢大家阅读我的博文。

posted @ 2019-09-30 04:50  _ann  阅读(332)  评论(0编辑  收藏  举报