代理模式
代理模式给某一个对象提供一下代理对象,并由代理对象控制对原对象的引用。在代理模式中(Proxy Pattern
)中,一个类代表另一个类的功能。这种类型的设计模式数据结构型模式。在代理模式中,我们创建具有现有对象的对象,以便向外界提供功能接口。
意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。
应用实例: 1、Windows
里面的快捷方式。 2、猪八戒去找高翠兰结果是孙悟空变的,可以这样理解:把高翠兰的外貌抽象出来,高翠兰本人和孙悟空都实现了这个接口,猪八戒访问高翠兰的时候看不出来这个是孙悟空,所以说孙悟空是高翠兰代理类。 3、买火车票不一定在火车站买,也可以去代售点。 4、一张支票或银行存单是账户中资金的代理。支票在市场交易中用来代替现金,并提供对签发人账号上资金的控制。 5、spring aop
。
优点: 1、职责清晰。 2、高扩展性。 3、智能化。
缺点: 1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。 2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
使用场景:按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write
代理。 4、保护(Protect or Access
)代理。 5、Cache
代理。 6、防火墙(Firewall
)代理。 7、同步化(Synchronization
)代理。 8、智能引用(Smart Reference
)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制
为什么要用代理模式
中介隔离作用
在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口
开闭原则
代理类出了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。
代理类主要负责为委托类预处理消息,过滤消息,把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。
例如:为项目加入缓存,日志等功能,可以使用代理类来完成,而没有必要打开已经封装好的委托类
静态代理
静态代理是由程序员创建或特定工具自动生成源代码,再对其编译,在程序运行之前,代理类就已经被创建了
1 | public interface Image { |
1 | public class RealImage implements Image { |
1 | public class ProxyImage implements Image { |
1 | public class ProxyPatternDemo { |
1 | public interface Image { |
1 | public class RealImage implements Image { |
1 | public class ProxyImage implements Image { |
静态代理总结
优点:可以做到在符合开闭原则的情况下,对目标对象进行功能扩展
缺点:为每一个服务都得创建一个代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改
动态代理
动态代理是程序运行时通过反射机制动态创建的
在动态代理中,我们不再需要手动的创建代理类,我们只需要编写一个动态处理器就可以了。真正的代理对象由JDK
在运行时为我们动态来创建
1 | public interface Image { |
1 | public class RealImage implements Image { |
1 | public class DynamicProxyHandle implements InvocationHandler { |
1 | public class DynamicProxyTest { |
注意
Proxy.newProxyInstance()
方法接收三个参数
- *
ClassLoader loader
:*指定当前目标对象使用的类加载器,获取加载器的方法是固定的 - *
Class<?>[] interfaces
:*指定目标对象实现的接口的类型,使用泛型方式确认类型 - *
InvocationHandler:
*指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
总结
虽然相对于静态代理,动态代理大大减少了开发量,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小遗憾,它始终无法摆脱仅支持interface
代理的桎梏,因为它的设计注定了这个遗憾。回想一下那些动态生成的代理类的继承关系图,它们已经注定有一个共同的父类叫Proxy
。
Java
的继承机制注定了这些动态代理类们无法实现对class
的动态代理,原因是多继承在Java
中本质上就行不通。
有很多理由,人们可以否定对class
代理的必要性,但是同样有一些理由,相信支持class
动态代理会更好。
接口和类的划分,本就不是很明显,只是到了Java
中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。
此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。但是,不完美并不等于不伟大,伟大是一种本质,Java
动态代理就是佐例。
JDK
动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理。如果要代理的类为一个普通类、没有接口,那么Java
动态代理就没法使用了
CGLIB代理
JDK
实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理,这就需要CGLIB
。CGLIB
采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的方法的调用,顺势植入横切逻辑。但因为采用的是继承,所以不能对final
修饰的类进行代理。JDK
动态代理与CGLIB
动态代理均是实现Spring AOP
的基础
1 | public class RealImage{ |
1 | public class CglibProxy implements MethodInterceptor { |
1 | public class CglibProxyTest { |
总结
CGLIB
创建的动态代理对象比JDK
创建的动态代理对象的性能更高,但是CGLIB
创建代理对象时所花费的时间却比JDK
多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB
合适,反之使用JDK
方式要更为合适一些。同时由于CGLib
由于是采用动态创建子类的方法,对于final
修饰的方法无法进行代理