做一个积极的人
编码、改bug、提升自己
我有一个乐园,面向编程,春暖花开!
一、开篇
Java 的代理便是客户类不再直接和委托类打交道, 而是通过一个中间层来访问, 这个中间层便是代理。为啥要这样呢, 是由于利用代理有 2 个上风:
可以隐蔽委托类的实现可以实现客户与委托类之间的解耦, 在不修正委托类代码的情形下能够做一些额外的处理我们举个很常见的例子: 工厂会生产很多的玩具, 但是我们买玩具都是到商店买的, 而不是到工厂去买的, 工厂怎么生产我们并不关心, 我们只知道到商店可以买到自己想要的玩具,并且,如果我们须要送人的话商店可以把这些玩具利用礼品盒包装。这个工厂便是委托类, 商店便是代理类, 我们便是客户类。
在 Java 中我们有很多场景须要利用代理类, 比如远程 RPC 调用的时候我们便是通过代理类去实现的, 还有 Spring 的 AOP 切面中我们也是为切面天生了一个代理类等等。
代理类紧张分为静态代理、JDK 动态代理和 CGLIB 动态代理,它们各有优缺陷,没有最好的, 存在便是故意义的,在不同的场景下它们会有不同的用武之地。
1. Java 静态代理
首先, 定义接口和接口的实现类, 然后定义接口的代理工具, 将接口的实例注入到代理工具中, 然后通过代理工具去调用真正的实现类,实现过程非常大略也比较随意马虎理解, 静态代理的代理关系在编译期间就已经确定了的。它适宜于代理类较少且确定的情形。它可实现在不修正委托类代码的情形下做一些额外的处理。
比如包装礼盒,实现客户类与委托类的解耦。缺陷是只适用委托方法少的情形下, 试想一下如果委托类有几百上千个方法, 岂不是很难熬痛苦, 要在代理类中写一堆的代理方法。这个需求动态代理可以搞定。
2. 动态代理技能
代理类在程序运行时创建的代理办法被认为 动态代理。在理解动态代理之前, 我们先回顾一下 JVM 的类加载机制中的加载阶段要做的三件事情
( 附 Java 中的类加载器 )
通过一个类的全名或其它路子来获取这个类的二进制字节流将这个字节流所代表的静态存储构造转化为方法区的运行时数据构造在内存中天生一个代表这个类的 Class 工具, 作为方法区中对这个类访问的入口而我们要说的动态代理,紧张就发生在第一个阶段, 这个阶段类的二进制字节流的来源可以有很多, 比如 zip 包、网络、运行时打算天生、其它文件天生 (JSP)、数据库获取。个中运行时打算生造诣是我们所说的动态代理技能,在 Proxy 类中, 便是利用了 ProxyGenerator.generateProxyClass 来为特定接口天生形式为 $Proxy 的代理类的二进制字节流。所谓的动态代理便是想办法根据接口或者目标工具打算出代理类的字节码然后加载进 JVM 中。实际打算的情形会很繁芜,我们借助一些诸如 JDK 动态代理实现、CGLIB 第三方库来完成的
另一方面为了让天生的代理类与目标工具 (便是委托类) 保持同等, 我们有 2 种做法:通过接口的 JDK 动态代理 和通过继续类的 CGLIB 动态代理。(还有一个利用了 ASM 框架的 javassist 太繁芜了,我还没研究过, 这里TODO下)
3. JDK 动态代理
在 Java 的动态代理中, 紧张涉及 2 个类,java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler
我们须要一个实现 InvocationHandler 接口的中间类, 这个接口只有一个方法 invoke 方法, 方法的每个参数的注释如下代码。
我们对处理类中的所有方法的调用都会变成对 invoke 方法的调用,这样我们可以在 invoke 方法中添加统一的处理逻辑(也可以根据 method 参数判断是哪个方法)。中间类 (实现了 InvocationHandler 的类) 有一个委托类工具引用, 在 Invoke 方法中调用了委托类工具的相应方法,通过这种聚合的办法持有委托类工具引用,把外部对 invoke 的调用终极都转为对委托类工具的调用。
实际上,中间类与委托类构成了静态代理关系,在这个关系中,中间类是代理类,委托类是委托类。然后代理类与中间类也构成一个静态代理关系,在这个关系中,中间类是委托类,代理类是代理类。也便是说,动态代理关系由两组静态代理关系组成,这便是动态代理的事理。
Demo 如下:
在上面的测试动态代理类中, 我们调用 Proxy 类的 newProxyInstance 方法来获取一个代理类实例。这个代理类实现了我们指定的接口并且会把方法调用分发到指定的调用处理器。
首先通过 newProxyInstance 方法获取代理类的实例, 之后就可以通过这个代理类的实例调用代理类的方法,对代理类的方法调用都会调用中间类 (实现了 invocationHandle 的类) 的 invoke 方法,在 invoke 方法中我们调用委托类的对应方法,然后加上自己的处理逻辑。
java 动态代理最大的特点便是动态天生的代理类和委托类实现同一个接口。java 动态代理实在内部是通过反射机制实现的,也便是已知的一个工具,在运行的时候动态调用它的方法,并且调用的时候还可以加一些自己的逻辑在里面。(附: Java 反射)
3.2 Proxy.newProxyInstance 源码阅读
上面说过, Proxy.newProxyInstance 通过反射机制用来动态天生代理类工具, 为接口创建一个代理类,这个代理类实现这个接口。详细源码如下:
4. CGLIB 动态代理
JDK 动态代理依赖接口实现,而当我们只有类没有接口的时候就须要利用另一种动态代理技能 CGLIB 动态代理。首先 CGLIB 动态代理是第三方框架实现的,在 maven 工程中我们须要引入 cglib 的包, 如下:
<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2</version></dependency>
CGLIB 代理是针对类来实现代理的,事理是对指定的委托类天生一个子类并重写个中业务方法来实现代理。代理类工具是由 Enhancer 类创建的。CGLIB 创建动态代理类的模式是:
查找目标类上的所有非 final 的 public 类型的方法 (final 的不能被重写)将这些方法的定义转成字节码将组成的字节码转换成相应的代理的 Class 工具然后通过反射得到代理类的实例工具实现 MethodInterceptor 接口, 用来处理对代理类上所有方法的要求对付须要被代理的类,它只是动态天生一个子类以覆盖非 final 的方法,同时绑定钩子回调自定义的拦截器。值得说的是,它比 JDK 动态代理还要快。值得把稳的是,我们传入目标类作为代理的父类。不同于 JDK 动态代理,我们不能利用目标工具来创建代理。目标工具只能被 CGLIB 创建。在例子中,默认的无参布局方法被利用来创建目标工具。
二、总结
静态代理比较随意马虎理解, 须要被代理的类和代理类实现自同一个接口, 然后在代理类中调用真正实现类, 并且静态代理的关系在编译期间就已经确定了。而动态代理的关系是在运行期间确定的。静态代理实现大略,适宜于代理类较少且确定的情形,而动态代理则给我们供应了更大的灵巧性。
JDK 动态代理所用到的代理类在程序调用到代理类工具时才由 JVM 真正创建,JVM 根据传进来的 业务实现类工具 以及 方法名 ,动态地创建了一个代理类的 class 文件并被字节码引擎实行,然后通过该代理类工具进行方法调用。我们须要做的,只需指定代理类的预处理、调用后操作即可。
静态代理和动态代理都是基于接口实现的, 而对付那些没有供应接口只是供应了实现类的而言, 就只能选择 CGLIB 动态代理了
JDK 动态代理和 CGLIB 动态代理的差异
JDK 动态代理基于 Java 反射机制实现, 必须要实现了接口的业务类才能用这种方法天生代理工具。CGLIB 动态代理基于 ASM 框架通过天生业务类的子类来实现。JDK 动态代理的上风是最小化依赖关系,减少依赖意味着简化开拓和掩护并且有 JDK 自身支持。还可以平滑进行 JDK 版本升级,代码实现大略。基于 CGLIB 框架的上风是无须实现接口,达到代理类无侵入,我们只需操作我们关系的类,不必为其它干系类增加事情量,性能比较高。描述代理的几种实现办法? 分别说出优缺陷?
代理可以分为 \公众静态代理\"大众 和 \"大众动态代理\公众,动态代理又分为 \"大众JDK 动态代理\"大众 和 \"大众CGLIB 动态代理\"大众 实现。
静态代理:代理工具和实际工具都继续了同一个接口,在代理工具中指向的是实际工具的实例,这样对外暴露的是代理工具而真正调用的是 Real Object.
优点:可以很好的保护实际工具的业务逻辑对外暴露,从而提高安全性。缺陷:不同的接口要有不同的代理类实现,会很冗余JDK 动态代理:
为理解决静态代理中,天生大量的代理类造成的冗余;
JDK 动态代理只须要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现,
jdk 的代理是利用反射天生代理类 Proxyxx.class 代理类字节码,并天生工具
jdk 动态代理之以是只能代理接口是由于代理类本身已经 extends 了 Proxy,而 java 是不许可多重继续的,但是许可实现多个接口
优点:办理了静态代理中冗余的代理实现类问题。缺陷:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛非常。CGLIB 代理:
由于 JDK 动态代理限定了只能基于接口设计,而对付没有接口的情形,JDK 办法办理不了;
CGLib 采取了非常底层的字节码技能,其事理是通过字节码技能为一个类创建子类,并在子类中采取方法拦截的技能拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。
实现办法实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
但是 CGLib 在创建代理工具时所花费的韶光却比 JDK 多得多,以是对付单例的工具,由于无需频繁创建工具,用 CGLib 得当,反之,利用 JDK 办法要更为得当一些。
同时,由于 CGLib 由于是采取动态创建子类的方法,对付 final 方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采取字节码增强技能,性能也不错。
缺陷:技能实现相对难明得些。
作者:树修,原文:https://www.jianshu.com/p/1682ed0d0c16