设计模式——代理模式(Proxy Pattern)
概述
?????? 代理模式是指为其他对象提供一种代理,以控制对这个对象的访问。代理对象在访问对象和目标对象之间起到中介作用。代理对象也可以在不修改目标对象的前提下,提供额外的功能操作,拓展目标对象的功能,比如说在目标对象的某个方法执行前后你可以增加一些自定义的操作。
?????? Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
?????? 代理(Proxy)模式分为三种角色:
??? 抽象角色(Subject): 通过接口或抽象类声明真实角色和代理对象实现的业务方法。
??? 真实角色(Real Subject): 实现了抽象角色中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
??? 代理角色(Proxy) : 提供了与真实角色相同的接口,其内部含有对真实角色的引用,它可以访问、控制或扩展真实角色的功能。
代理模式结构图如下所示:
静态代理
?????? 静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展,静态代理需要代理对象和目标对象实现一样的接口。实现步骤如下:
- 定义一个接口及其实现类;
- 创建一个代理类同样实现这个接口
- 将目标对象注入进代理类,然后在代理类的对应方法调用目标类中的对应方法。
这样的话,我们就可以通过代理类屏蔽对目标对象的访问,并且可以在目标方法执行前后做一些自己想做的事情。
抽象主题类声明了真实主题类和代理类的公共方法,它可以是接口、抽象类或具体类,客户端针对抽象主题类编程,一致性地对待真实主题和代理主题,典型的抽象主题类代码如下:
真实主题类继承了抽象主题类,提供了业务方法的具体实现,其典型代码如下:
代理类也是抽象主题类的子类,它维持一个对真实主题对象的引用,调用在真实主题中实现的业务方法,在调用时可以在原有业务方法的基础上附加一些新的方法来对功能进行扩充或约束,最简单的代理类实现代码如下:
可以从上面代码看到,我们访问的是ProxyStation对象,也就是说ProxyStation是作为访问对象和目标对象的中介的,同时也对saleTickets方法进行了增强与扩展(代理点收取加收5%手续费)。
?????? 静态代理的优点是实现简单,容易理解,只要确保目标对象和代理对象实现共同的接口或继承相同的父类就可以在不修改目标对象的前提下进行扩展。
而缺点也比较明显,那就是代理类和目标类必须有共同接口(父类),并且需要为每一个目标类维护一个代理类,当需要代理的类很多时会创建出大量代理类。一旦接口或父类的方法有变动,目标对象和代理对象都需要作出调整。
动态代理
?????? 代理类在代码运行时创建的代理称之为动态代理,相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(CGLIB 动态代理机制)。从 JVM 角度来说,动态代理中代理类并不是预先在Java代码中定义好的,而是运行时由JVM动态生成,并且可以代理多个目标对象。动态代理是在 运行时 动态生成类字节码,并加载到 JVM 中的。说到动态代理,Spring AOP、RPC 框架应该是两个不得不提的,它们的实现都依赖了动态代理。动态代理在我们日常开发中使用的相对较少,但是在框架中的几乎是必用的一门技术。学会了动态代理之后,对于我们理解和学习各种框架的原理也非常有帮助。就 Java 来说,动态代理的实现方式有很多种,比如 JDK 动态代理、CGLIB 动态代理 等等。
JDK动态代理
JDK动态代理是Java JDK自带的一个动态代理实现, 位于java.lang.reflect包下。在 Java 动态代理机制中 InvocationHandler 接口和 Proxy 类是核心。Proxy类中使用频率最高的方法是:newProxyInstance() ,这个方法主要用来生成一个代理对象。
这个方法一共有 3 个参数:
- loader :类加载器,用于加载代理对象。
- interfaces : 被代理类实现的一些接口;
- h : 实现了 InvocationHandler 接口的对象;
要实现动态代理的话,还必须需要实现InvocationHandler 来自定义处理逻辑。
当我们的动态代理对象调用一个方法时,这个方法的调用就会被转发到实现InvocationHandler 接口类的 invoke 方法来调用。
invoke() 方法有下面三个参数:
- proxy :动态生成的代理类
- method : 与代理类对象调用的方法相对应
- args : 当前 method 方法的参数
JDK动态代理使用步骤
1、定义一个接口及其实现类;
2、自定义 InvocationHandler 并重写invoke方法,在 invoke 方法中我们会调用原生方法(被代理类的方法)并自定义一些处理逻辑;
3、通过 Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) 方法创建代理对象;
接口及实现类同静态代码,主要看下代理类:
优点:
- 使用简单、维护成本低。
- Java原生支持,不需要任何依赖。
- 解决了静态代理存在的多数问题。
缺点:
- 由于使用反射,性能会比较差。
- 只支持接口实现,不支持继承, 不满足所有业务场景。
CGLIB动态代理
介绍JDK 动态代理有一个最致命的问题是其只能代理实现了接口的类。 为了解决这个问题,我们可以用 CGLIB动态代理机制来避免。CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。
CGLIB通过继承方式实现代理。很多知名的开源框架都使用到了CGLIB, 例如 Spring 中的 AOP模块中:如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
在 CGLIB 动态代理机制中MethodInterceptor 接口和 Enhancer 类是核心。你需要自定义 MethodInterceptor 并重写intercept 方法,intercept 用于拦截增强被代理类的方法。
??? obj : 被代理的对象(需要增强的对象)
??? method : 被拦截的方法(需要增强的方法)
??? args : 方法入参
??? proxy :用于调用原始方法
你可以通过 Enhancer类来动态获取被代理类,当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的intercept 方法。
CGLIB动态代理使用步骤:
- 定义一个类;
- 自定义 MethodInterceptor 并重写 intercept 方法,intercept 用于拦截增强被代理类的方法,和 JDK 动态代理中的 invoke 方法类似;
- 通过 Enhancer 类的 create()创建代理类。
不同于 JDK 动态代理不需要额外的依赖。CGLIB(Code Generation Library) 实际是属于一个开源项目,如果你要使用它的话,需要手动添加相关依赖。
<dependency>
? <groupId>cglib</groupId>
? <artifactId>cglib</artifactId>
? <version>3.3.0</version>
</dependency>
应用场景:
??? 保护目标对象。
??? 增强目标对象。
优点:
??? 代理模式能将代理对象与真实被调用的目标对象分离。
??? 一定程度上降低了系统的耦合程度,易于扩展。
??? 代理可以起到保护目标对象的作用。
??? 增强目标对象的职责。
缺点:
??? 代理模式会造成系统设计中类的数目增加。
??? 在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢。
??? 增加了系统的复杂度。
JDK 动态代理和 CGLIB 动态代理对比
JDK动态代理的特点:
??? 需要实现InvocationHandler接口, 并重写invoke方法。
??? 被代理类需要实现接口, 它不支持继承。
??? JDK 动态代理类不需要事先定义好, 而是在运行期间动态生成。
??? JDK 动态代理不需要实现和被代理类一样的接口, 所以可以绑定多个被代理类。
??? 主要实现原理为反射, 它通过反射在运行期间动态生成代理类, 并且通过反射调用被代理类的实际业务方法。
cglib的特点:
??? cglib动态代理中使用的是FastClass机制。
??? cglib生成字节码的底层原理是使用ASM字节码框架。
??? cglib动态代理需创建3份字节码,所以在第一次使用时会比较耗性能,但是后续使用较JDK动态代理方式更高效,适合单例bean场景。
??? cglib由于是采用动态创建子类的方法,对于final方法,无法进行代理。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!