关于JDK动态代理的自我理解

前言:

       关于学习,我总是在探索适合自己的理解方式,因为我是理科思维,思考方式偏向于"因为-所以",因此思考事情喜欢弄清前因后果、来龙去脉、前世今生...我一直相信坚信:知其然更要知其所以然,无论技术还是人生。

       从学生时代copy老师的解题步骤,到懂得照猫画虎、举一反三,一路到如今学会独立思考、深究底层、换位为开发者进行考虑,我认为最适合自己的学习方法是:

       明白它的用途----描绘出所涉及知识的关联关系----找出关键点----从关键点切入,前后发散----有了轮廓以后,再次总览全局,找出最适合自己的理解方式,然后将自己化身程序,钻进电脑跑上一遍。

      当然,要想掌握一项技术,自认为会了是不够的,还需要整理文档记录在册,这其实是在整理自己的思路,记录下来的知识为了以后查看,没理由不做到思路清晰,而且涉及全面。能做到这一点,差不多能够掌握7层了,因为你做到了思路清晰、扫除盲点。

      但却还没有融会贯通,如同绝世高手,没有一个是只凭修炼就能达成的,没有实战、没有切磋交流,一切更像是纸上谈兵。所以最后一步,就是要将你所掌握尽数拿来交流与实践。我们并不是世上最聪明的人,对于一项技术,并非就只有自己的看方法是正确的,集思广益,以他人的思维说出你的理解、接受他人的意见并取其精华,才有望大成。

       回转话题,来看这次的主题:

JDK动态代理

       不同于我在博客上见到的其他JDK动态代理文章,本篇我以一个关键点切入,前后发散,说明JDK动态代理的优点以及支持此优点的必须条件,此思路易于我的理解,希望能对各位看官有所帮助。

首先说一下静态代理与JDK动态代理的区别:

静态代理是在编写代码时,即运行前就已经将接口、代理类、被代理类确定下来了,在程序运行之前, 代理类的.class文件就已经生成。 
JDK动态代理是在程序运行时再创建代理类的,是动态生成的。

如果仅仅这个区别,编写代码时不见得有什么不同。但是出现这个情况的话,就能凸显动态代理的好处了:

例如:我要让每个代理方法在执行都记录下执行的时间是多少,怎么实现呢?在代理类执行方法前后,分别写两个获取时间的方法,然后相减得到期望结果。 
那问题来了:如果这个代理的方法有很多很多,那是不是要写相同个数的事件处理方法?很不划算。

而动态代理就有这样的特点: 
     它将代理的所有方法,全部抛给一个叫做invoke(...)的方法中,所有的方法都在invoke(...)中执行 
那么我只要在invoke(...)的方法头尾,加上获取时间的方法,是不是一劳永逸,管它有多少个代理方法,我只针对invoke(...)写一次就够了?

效果很理想,那么接下来就看看如何实现的:

JDK动态代理的实现原理

首先,invoke(...)这个方法哪儿来的,干什么的?

invoke(...)存在于InvocationHandler接口中,这个接口是JDK提供的动态代理接口,invoke(...)实际长成这样:

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

有接口就有实现类,它的实现类长这样:

public class MyInvocationHandler implements InvocationHandler{
  @Override
  public Object invoke(Object proxy,Method method,Object[] args) throw Exception{
    return null;
  }
}

这是实现类啊,我们就可以在里面写代码了对吧 
那invoke(...)是做什么的?上面说了,JDK动态代理就是将代理的方法全部扔给invoke(...),在它里面执行,再想想invoke的中文含义:调用! 
在学习反射时,是不是有个invoke()方法,作用是调用某个对象的某个方法,格式什么样的?方法名.invoke(对象名,参数);那这里同样的写法:

public class MyInvocationHandler implements InvocationHandler{
  public Object invoke(Object proxy,Method method,Object[] args)throw Exception{
    Object result = method.invoke(target,args);
    return result;
  }
}

这个method和args就是invoke(...)接收来的,那target呢?肯定是对象名了,什么对象,"代理"它代理的不就是"本尊"嘛,可是invoke(...)不用你传给我"本尊"对象啊,为什么不传呢:invoke(...)需要"本尊",也可以说是MyInvocationHandler这个对象需要它,那么MyInvocationHandler自己关联"本尊",不就解决了吗

public class MyInvocationHandler implements InvocationHandler{
  public Object invoke(Object proxy,Method method,Object[] args)throw Exception{
    //这个Object代表的就是"本尊",因为类型不确定,所以定义为Object
    private Object target;
    //这是MyInvocationHandler类的构造器
    public MyInvocationHandler(Object object){
      this.target = subject;
    }
    Object result = method.invoke(target,args);
    return result;
  } 
{

看,是不是搞定了。

那我们再理一下思路,我要通过代理访问到"本尊"的方法,为了便于统一管理,执行时将这些方法全都抛给了invoke(...),并告诉它,方法名是什么,参数是什么,而对象你自己有,万事俱备,你就帮我访问了"本尊"吧

那现在疑问就是: 
1.谁将方法抛给了invoke(...) 
2.它是怎么找到invoke(...)的 
3.就算你找到了,你怎么能确定抛过去的方法,就肯定是MyInvocationHandler对象封装的"本尊"的方法呢

代理模式就是通过代理来访问"本尊",而不是让你直接访问到他,所以"谁"就是代理了。可是动态代理在编译阶段还不存在代理类的,怎么办? 
Java中为我们提供了一个Proxy类,其中有个newProxyInstance(...)的静态方法,见名知意,这个方法就是"创建代理实例"用的。 
看看这个方法具体参数: 
      newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h>); 
loader为类加载器 
interfaces为数组,里面放入不知何种类型的类 
h,看看他的类型,是不是将将MyInvocationHandler对象传过来的?

再来看看这个方法里的关键代码部分(只提取除了少量代码):

>
//将传进来的数组复制出一份不可变的出来
  final Class<?>[] intfs = interfaces.clone()
//这就是关键了,这一步根据传进来的两个参数创建了一个类,它就是代理类
  Class<?> cl = getProxyClass0(loader,intfs);
//得到这个类的构造器
  final Constructor<?> cons = cl.getConstructor(constructorParams);
//返回new出来的对象
  return cons.newInstance(new Object[]{h});

其实这个源码,就是封装了创建动态代理类的步骤。

那么就解释了第一个疑问,JDK提供了一个静态方法,在运行时创建代理类的实例,就是这个实例把所有需要代理的方法抛给了invoke(...)

"代理"要与"本尊"建立联系,有与其相同的方法,才能做到代理,所以newProxyInstance(...)的前两个参数,传进来的就是"本尊"对象的 
这两个参数可以保证在getProxyClass0(loader,intfs)时,得到"本尊"的全部方法,也就是其实现的接口所规定的全部方法 
那么回想一下,"本尊"给了MyInvocationHandler一份,又给了代理对象一份,那么代理对象抛给MyInvocationHander中invoke(...)的方法,不就肯定能和后者中的target对象匹配吗,第三个疑问也就解决了

那么看第二个疑问,怎么找到的invoke(...)?

当程序运行创建了代理类后,通过反编译这个代理类,能看到这样几个关键的地方:

>
//m3是返回的"本尊的某方法"
  m3 = Class.forName("proxy.本尊").getMethod("本尊的某方法",new Class[0]);
  public final void 本尊的某方法(){
    this.h.invoke(this,m3,方法参数);
  }
//这是代理类的构造器,调用的父类构造,传入的是InvocationHandler
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws
{
    super(paramInvocationHandler);
}

现在知道向newProxyInstance(...)中传入MyInvocationHandler的作用了吧,以及它是怎么传入的,目的就是为了找到后者中的invoke(...),好让我能抛方法给它,让它去执行

总结

再来梳理一遍流程,我想要通过代理访问"本尊",那代理怎么拥有的"本尊"方法呢? 
因为JDK提供了newProxyInstance静态方法,它让我们提供"本尊"的参数,那不就有了"本尊"的一切吗 
有了方法,为了便于管理、统一操作,代理就将统统方法抛给了中间方MyInvocationHander,请它用invoke(...)代为调用"本尊"方法 
为了让中间方知道代理给我的方法到底是谁的,那就在中间方和代理中各自传入"本尊",这不就对应上了吗

统一管理,相当于一个方法收集器,无论有多少方法,都是传入invoke(...)中,那么能在invoke(...)里处理的逻辑就很多了 
比如账号登录的提示通知,获取任务执行的耗时等等

 

鉴于本人水平有限,如有错误,欢迎各位看官指正,学习本就是不断碰壁又不断摸索的过程。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值