一、概述
(1)由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象。代理对象作为访问对象和模板对象之间的中介。
(2)Java 中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在 Java 运行时动态生成。动态代理又有 JDK 代理和 CGLib 代理两种。
二、结构
代理(Proxy)模式分为三种角色:
(1)抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
(2)真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象说代表的真实对象,是最终要引用的对象。
(2)代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
三、静态代理
四、JDK 动态代理
Java 中提供了一个动态代理类 Proxy,Proxy 并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance 方法)来获取代理对象。
思考:
ProxyFactory 不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。
通过阿里巴巴开源的 Java 诊断工具(Arthas)查看代理类的结构:
获取到的信息:
(1)代理类($Proxy0)实现类 SellTickets 。这也就印证了 真实类和代理类实现同样的接口。
(2)代理类将我们提供了的匿名内部类对象传递给了父类。
执行流程:
1. 在测试类中通过代理对象调用 shell() 方法
2. 根据多态的特性,执行的是代理类($Proxy0)中的 sell() 方法
3. 代理类($Proxy0)中的 sell() 方法中又调用了 InvocationHandler 接口的子实现类对象的 invoke 方法
4. invoke 方法通过反射执行了真实对象所属类(TrainStation)中的 sell() 方法
五、CGLIB 动态代理
六、三种代理的对比
jdk 代理和 cglib 代理
使用 cglib 实现动态代理,cglib 底层采用 ASM 字节码生成框架,使用字节码技术生成代理类,在 JDK1.6 之前比使用 Java 反射效率高。唯一需要注意的是,cglib 不能对声明为final 的类或者方法进行代理,因为 cglib 原理是动态生成被代理类的子类。
JDK1.6、JDK1.7、JDK1.8 逐步对 JDK 动态代理优化之后,在调用次数较少的情况下,JDK 代理效率高于 cglib 代理效率,只有当进行大量调用的时候,JDK1.6 和 JDK1.7 比cglib 代理效率低一点,但是 JDK1.8 的时候,JDK 代理效率高于 cglib 代理。所以如果有接口使用 JDK 动态代理。
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,可以灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有带来了也需要实现此方法。增加了代码维护的复杂程度。而动态代理不出出现该问题。
七、优缺点
优点:
1. 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用。
2. 代理模式对象可以扩展模板对象的功能
3. 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度
缺点:
1. 增加了系统的复杂度。
八、使用场景
1. 远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
2. 防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就讲你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
3. 保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。