关于学习,我总是在探索适合自己的理解方式,因为我是理科思维,思考方式偏向于"因为-所以",因此思考事情喜欢弄清前因后果、来龙去脉、前世今生...我一直相信坚信:知其然更要知其所以然,无论技术还是人生。
从学生时代copy老师的解题步骤,到懂得照猫画虎、举一反三,一路到如今学会独立思考、深究底层、换位为开发者进行考虑,我认为最适合自己的学习方法是:
明白它的用途----描绘出所涉及知识的关联关系----找出关键点----从关键点切入,前后发散----有了轮廓以后,再次总览全局,找出最适合自己的理解方式,然后将自己化身程序,钻进电脑跑上一遍。
当然,要想掌握一项技术,自认为会了是不够的,还需要整理文档记录在册,这其实是在整理自己的思路,记录下来的知识为了以后查看,没理由不做到思路清晰,而且涉及全面。能做到这一点,差不多能够掌握7层了,因为你做到了思路清晰、扫除盲点。
但却还没有融会贯通,如同绝世高手,没有一个是只凭修炼就能达成的,没有实战、没有切磋交流,一切更像是纸上谈兵。所以最后一步,就是要将你所掌握尽数拿来交流与实践。我们并不是世上最聪明的人,对于一项技术,并非就只有自己的看方法是正确的,集思广益,以他人的思维说出你的理解、接受他人的意见并取其精华,才有望大成。
回转话题,来看这次的主题:
不同于我在博客上见到的其他JDK动态代理文章,本篇我以一个关键点切入,前后发散,说明JDK动态代理的优点以及支持此优点的必须条件,此思路易于我的理解,希望能对各位看官有所帮助。
首先说一下静态代理与JDK动态代理的区别:
静态代理是在编写代码时,即运行前就已经将接口、代理类、被代理类确定下来了,在程序运行之前, 代理类的.class文件就已经生成。 JDK动态代理是在程序运行时再创建代理类的,是动态生成的。
如果仅仅这个区别,编写代码时不见得有什么不同。但是出现这个情况的话,就能凸显动态代理的好处了:
例如:我要让每个代理方法在执行都记录下执行的时间是多少,怎么实现呢?在代理类执行方法前后,分别写两个获取时间的方法,然后相减得到期望结果。 那问题来了:如果这个代理的方法有很多很多,那是不是要写相同个数的事件处理方法?很不划算。
而动态代理就有这样的特点: 它将代理的所有方法,全部抛给一个叫做invoke(...)的方法中,所有的方法都在invoke(...)中执行 那么我只要在invoke(...)的方法头尾,加上获取时间的方法,是不是一劳永逸,管它有多少个代理方法,我只针对invoke(...)写一次就够了?
效果很理想,那么接下来就看看如何实现的:
首先,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(...)里处理的逻辑就很多了 比如账号登录的提示通知,获取任务执行的耗时等等
鉴于本人水平有限,如有错误,欢迎各位看官指正,学习本就是不断碰壁又不断摸索的过程。