jdk动态代理中的Proxy类与中间层InvocationHandler的深入研究总结

jdk动态代理中的Proxy类与中间层InvocationHandler的深入研究总结,第1张

一:jdk动态代理的逻辑介绍

动态生成的代理类里面每一个方法的实现都是调用InvocationHandler的invoke方法,在invoke()方法中,有三个参数,分别是Object,Method以及args数组,三个参数都是代理类Proxy调用时传入的。第一个参数Proxy传入的是当前的this对象;第二个参数Proxy传入的是需要被执行的Method对象;第三个参数则是Method对象需要的参数。因此,代理类只需要拿到对应的Method对象及相应参数,就可以通过InvocationHandler的invoke()方法对功能进行增强。代理类及Method对象都是jvm运行时通过反射生成的,因此jdk动态代理的底层就是反射

二:代理类是如何生成的

1.生成Object类的几个固有方法,包括toString(),hashCode(),equals()等

//代理类默认实现了被代理的接口以及继承了Proxy父类
//代理类在构造函数中,会把InvocationHandler实例交给父类Proxy
    public $proxy(InvocationHandler var1) throws  {
        super(var1);
    }
//代理类中的equals方法实现
public final boolean equals(Object var1) throws  {
    try {
        //super.h.invoke()表示调用父类的h的invoke方法,m1则是要代理的方法,该方法我们
        //会在InvocationHandler实例中,通过method.invoke()进行执行
        return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
    } catch (RuntimeException | Error var3) {
        throw var3;
    } catch (Throwable var4) {
        throw new UndeclaredThrowableException(var4);
    }
}
//代理类中获取被代理方法的method对象
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));

因此,需要传入类加载器与InvocationHandler实例

2.生成代理的方法

Proxy会拿到接口,遍历接口的所有方法,然后生成代理类的代理方法以及接口的Method对象,因此,生成代理类时,需要传入对应的接口参数。代理方法逻辑实现与上述逻辑一样,只是对象由Object变为了特定接口对象而已。

//代理接口方法,实质还是调用了InvocationHandler实例的invoke方法,只是参数method对象
//变成了该方法要代理的method对象m3
public final void toSchool() throws IOException {
    try {
        super.h.invoke(this, m3, (Object[])null);
    } catch (RuntimeException | IOException | Error var2) {
        throw var2;
    } catch (Throwable var3) {
        throw new UndeclaredThrowableException(var3);
    }

}

//拿到接口方法的method对象

m3 = Class.forName("test_procuration.Do").getMethod("toSchool");

因此真正执行逻辑是,代理类先执行自己的代理方法,进而调用InvocationHandler的invoke()方法,然后在invoke方法内,调用代理类传入的对应method对象,进而完成方法的代理。而method方法的执行者,则可以直接固定,一般指定为被代理对象,即被代理对象来执行该方法。因此,InvocationHandler需要持有被代理对象的引用。

(直接依赖接口或者接口实现类都可以,一般为了可扩展性,都会选择面向抽象,即让InvocationHandler内部依赖被代理接口,在外部传入接口实现类给InvovationHandler实例即可,因此需要代理同一接口的实现类时,若增强功能相同,代码是不需要变动的,只需要外部传递对应的接口实现类即可)

简而言之:生成的代理类会继承Proxy类以及实现执行的接口,从而完成接口方法的代理。代理方法会拿到父类Proxy的InvocatioinHandler实例(所以创建Proxy时要传入InvocationHandler实例),进而调用其invoke()方法。

三:几个有趣的坑

        代理类与被代理类肯定不是同一个类,这毋庸置疑。但是如果直接输出两个对象会发现两个对象是同一个对象,哈希码也是相同的。但是通过“==”b比较两个对象却会返回false(证明不是同一个对象)。原因是输出被代理对象时,调用的是代理对象的toString()方法,根据代理逻辑,会去执行InvocationHandler的invoke()方法,进而执行方法里面的method.invoke()方法,问题就出现在这里,一般我们代理都是使用被代理对象去执行该方法的,即method.invoke(target,args),也就是使用target对象执行method的底层方法,完成方法的调用。即看似代理对象执行了toString()方法,实际是被代理对象执行了toString()方法,因此,无论怎么调用,拿到的都是被代理对象的hashCode。

        那是不是把target换成是this,就可以执行this的方法,而前面说的该this是代理类传入的当前实例对象,即可以执行代理类自身的方法呢?其实如果换成this,是无法完成代理,生成无法完成对应mehod的调用的(method对象都不是自己的,自己都不是method对象对应的实例对象,如何调用?)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/756790.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-30
下一篇 2022-04-30

发表评论

登录后才能评论

评论列表(0条)

保存