《手绘6张图彻底搞懂动态代理.docx》由会员分享,可在线阅读,更多相关《手绘6张图彻底搞懂动态代理.docx(8页珍藏版)》请在第一文库网上搜索。
1、手绘6张图彻底搞懂动态代理在讲解动态代理前我们先聊聊什么是静态代理。静态代理假设有一天领导突发奇想,给你下发了一个需求:统计项目中所有类的方法执行耗时。在拿到需求的那一刻,脑海中冒出来的第一个想法是:在每个方法的第一行和最后一行加上时间埋点,再打印一行志不就完事了。抄起键盘准备开干,想了想又开始犹豫了:在每个方法都加几行代码,这不是侵入式修改吗?听架构师大佬说这样的场景可以用代理模式,那尝试一下,具体做法如下。静态代理的实剧(1)为工程里每个类都写一个代理类,让它与目标类实现同一个接口。图中标红色的就是代理类。 wofk()AProxyAlmpl+ wofk()BProxylnterfaceB
2、(2)在代理类里面维护一个Fl标实现类,调用代理类的方法时还是会去调用目标类的方法,只不过在前后加了一些其他逻辑代码。也就是说后面客户端不需要直接调用目标实现类,只需要调用代理类即可,这样就间接调用了对应方法。用一个公式总结一下:代理类=增强代码+目标实现类。下面这个图中,计算耗时的逻辑就是增强代码。(3)在所有new目标类的地方都替换为new代理类,并将目标类作为构造方法参数传入;所有使用目标类调用的地方全部都替换为代理类调用。如果你看懂了上面的实现方法,那么恭喜你已经掌握了静态代理的核心思想。静态代理的缺司静态代理的思路非常简单,就是给每一个目标实现类写一个对应的代理实现类,但是如果一个项
3、目有几千甚至有几万个类,这个工作量可想而知。前面我们还隐藏了一个假设:每个类都会实现一个接口。那如果一个类没有实现任何接口,代理类如何实现呢?成Class对象;好了,我们来总结一下静态代理的缺点:静态代理需要针对每个目标实现类写一个对应的代理类,如果目标类的方法有变动,代理类也要跟着动,维护成本非常高。静态代理必须依赖接口。既然知道了静态代理的缺点,那有没有办法实现少些或者不写代理类来实现代理功能呢?答案是有,动态代理。对象的创建流程在正式介绍动态代理前,我们先复习一下java中对象是如何创建的。JVM运行时区域我们在项目中使用一行代码就可以简单创建一个对象,实际上经过的流程还是很复杂的。(1
4、) java源文件经过编译生成字节码文件(.class结尾);(2)类加载器将class文件加载到JVM内存中,就是常说的方法区,生(3)执行new,申请一块内存区域,紧接着创建一个对象放在JVM对象,准确地说是新生代;上面的流程中提到了 Class对象,有两个概念初学者很容易混淆:Class对象和Class对象简单来说就是Class类的实例,Class类描述了所有的类;实例对象是通过Class对象创建出来的。从上面的分析可以看出来,要想创建一个实例,最最关键的是获得Class对象。有些同学可能有疑问了,我写代码的时候创建对象没有用到Class对象呀,那是因为Java语言底层帮你封装了细节。J
5、ava语言给我们提供了 new这个关键字,new实在太好用了,一行代码就可以创建一个对象。我们再问到前面讲的静态代理,静态代理最重要的是提前写一个代理类,有了代理类就可以new 一个代理对象。但是每次都去写一个代理类是不是太麻烦了? !再稍微扩展一下思路,有没有办法不写代理类还能生成一个代理对象呢?可以,上面讲的通过代理类Class对象就可以生成代理对象,那如何获取代理类Class对象呢?我们接着往下看。动态代理Class对象包含了一个类的所有信息,如:构造方法、成员方法、成员属性等。如果我们不写代理类,似乎无法获得代理类Class对象,但稍稍动一动脑:代理类和目标类实现的是同一组接口,是不是
6、可以通过接口间接获得代理类Class对象。代理类和目标类实现了同一组接口,这就说明他们大体结构都是一致的,这样我们对代理对象的操作都可以转移到目标对象身上,代理对象只需要专注于增强代码的实现。上面说了这么多其实是在引入动态代理的概念,动态代理相对于静态代理最大的区别就是不需要事先写好代理类,一般在程序的运行过程中动态产生代理类对象。动态代理实现之jdk|JDK原生提供了动态代理的实现,主要是通过java. lang. ref lect. Proxy和java. lang. ref lect InvocationHandler 这两个类配合使用。Proxy类有个静态方法,传入类加载器和一组接口就
7、可以返回代理Class对象。public static Class getProxyClass(ClassLoader loader, Class.i nterfaces)这个方法的作用简单来说就是,会将你传入一组接口类的结构信息拷贝到一个新的Class对象中,新的Class对象带有构造器是可以创建对象的。,句话总结:Proxy. getProxyClass () 这个静态方法的本质是以Class造Classo拿到了 Class对象,就可以使用反射创建实例对象了:Proxy. getProxyClass默认会生成一个带参数的构造方法,Constructor constructor = aCla
8、zz.getConstructor (Invocat ionHandler class);使用反射创建代理而A al = constructor. newTnstance (new Invocat i onllandl er ();眼尖的同学已经看到了,创建实例的时候需要传入一个Invocationllandler对象,说明代理对象中必然有一个成员变量去接收。在调用代理对象的方法时实际上会去执行InvocationHandler对象的invoke方法,画个图理解一卜:invoke方法里可以写增强代码,然后调用目标对象work方法。总结一下流程:(1 )通过Proxy. getProxyClas
9、s ()方法获取代理类Class对象;(2 )通过反射aClazz. getConstructor ()获取构造器对象;(3)定义InvocationHandler类并实例化,当然也可以直接使用匿名内部类;(4)通过反射constructor, newlnstance ()创建代理类对象;(5)调用代理方法;看了上面的流程,是不是觉得比静态代理还要繁琐,有没有更加优雅的方法?当然有!为了尽量简化操作,JDK Proxy类直接提供了一个静态方法:public static Object newProxylnstance(ClassLoader loader, Classinterfaces, I
10、nvocationllandler h)这个方法传入类加载器、一组接口和Invocationllandler对象直接就可以返回代理对象了,有了代理对象就可以调用代理方法了,是不是so easy? !newProxylnstance方法本质上帮我们省略了获取代理类对象和通过代理类对象创建代理类的过程,这些细节全部隐藏了。所以真正在项目中直接使用newProxylnstance这个方法就好了,上面讲的那些流程是为了方便大家理解整个过程。看到这里我相信大家应该能看懂JDK原生动态代理了。动态代理实现之cglib|JDK动态代理,一旦目标类有了明确的接口,完全可以通过接口生成一个代理Class对象,通
11、过代理Class对象就可以创建代理对象。这里可以看出JDK动态代理有个限制必须要求目标类实现了接口,那加入一个目标类没有实现接口,那岂不是不能使用动态代理了?cglib就是为了实现这个目标而出现的,利用asm开源包对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。JDK动态代理与cglib动态代理对比我们通过几个问题简单对比一下JDK和cglib动态代理的区别。问题1: cglib和JDK动态代理的区别? JDK动态代理:利用InvocationHandler加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理 cglib动态代理:利用
12、ASM框架,将目标对象类生成的class文件加载进来,通过修改其字节码生成代理子类问题2: cglib比JDK快? cglib底层是ASM字节码生成框架,在JDK 1.6前字节码生成要比反射的效率高 在JDKL6之后JDK逐步对动态代理进行了优化,在1.8的时候JDK的效率已经高于cglib问题3: Spring框架什么时候用cglib什么时候用JDK动态代理? 目标对象生成了接口默认用JDK动态代理 如果目标对象没有实现接口,必须采用cglib 当然如果目标对象使用了接口也可以强制使用cglib小结使用代理模式可以避免侵入式修改原有代码。代理分为:静态代理和动态代理。静态代理要求目标类必须实
13、现接口,通过新建代理类并且与目标类实现同一组接口,最终实现通过代理类间接调用目标类的方法。关于代理类,可以用一个公式总结一下:代理类=增强代码+目标实现类。静态代理必须要求提前写好代理类,使用起来比较繁琐,这就引入了动态代理。动态代理是在程序运行的过程中动态生成代理类,根据实现方式的不同进而分为:JDK原生动态代理和CGLIB动态代理。JDK动态代理通过反射+ InvocationHandler机制动态生成代理类来实现,要求目标类必须实现接口。cglib不要求目标类实现接口,通过修改字节码方式生成目标类的子类,这就是代理类。动态代理不仅在RPC框架中被使用,还在其他地方有着广泛的应用场景,比如:Spring AOP、测试框架mock、用户鉴权、日志、全局异常处理、事务处理等。大家学会了吗?