前言:
关于学习,我总是在探索适合自己的理解方式,因为我是理科思维,思考方式偏向于"因为-所以",因此思考事情喜欢弄清前因后果、来龙去脉、前世今生...我一直相信坚信:知其然更要知其所以然,无论技术还是人生。
从学生时代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(...)里处理的逻辑就很多了
比如账号登录的提示通知,获取任务执行的耗时等等
鉴于本人水平有限,如有错误,欢迎各位看官指正,学习本就是不断碰壁又不断摸索的过程。