【结构型】代理模式

noah2021

设计模式|2021-11-5|最后更新: 2024-9-21|
type
status
date
slug
summary
tags
category
icon
password

静态代理

意图

每个原始类都有一个代理类,他们共同实现一个接口,在不改变原始类(或叫被代理类)的情况下,通过引入代理类来给原始类附加功能

类图

一般情况下,我们让代理类和原始类实现同样的接口。但是,如果原始类并没有定义接口,并且原始类代码并不是我们开发维护的。在这种情况下,我们可以通过让代理类继承原始类的方法来实现代理模式
notion image

实战案例

实现步骤:
  1. 定义被代理接口:被代理的行为
  1. 实现被代理类
  1. 代理类组合接口或其实现类并构造,然后织入逻辑达到功能增强的目的
例一:未使用代理模式之前,Image 必须从磁盘中加载,然后展示出来。在修改成代理模式之后,做到了在不更改原始类的基础上添加了以下功能:若图片不存在,则从磁盘上加载后再展示出来;若图片存在,则直接展示出来。
例二:以支付宝为例,当我们使用支付宝付款时不用考虑怎样从银行取款以及怎样付款给慕课网,这一切都有支付宝帮我们做了

动态代理

静态代理的缺点是显而易见的。一方面,我们需要在代理类中,将原始类中的所有的方法,都重新实现一遍,并且为每个方法都附加相似的代码逻辑。另一方面,如果要添加的附加功能的类不止一个,我们需要针对每个类都创建一个代理类。我们可以使用动态代理来解决这个问题。

意图&实现

所谓动态代理 ( Dynamic Proxy ),就是我们不事先为每个原始类编写代理类,而是在运行的时候动态地创建原始类对应的代理类,然后在系统中用代理类替换掉原始类。

实战案例

AOP(底层原理)

  • 第一种 有接口情况,使用 JDK 动态代理(创建接口实现类代理对象,增强类的方法)
notion image
  • 第二种 没有接口情况,使用CGLIB动态代理
notion image
  • Spring AOP 默认使用 JDK 动态代理,这样便可以代理所有的接口类型(interface)
  • Spring AOP 也支持 CGLIB 的代理方式。如果我们被代理对象没有实现任何接口,则默认是 CGLIB
  • 我们可以强制使用 CGLIB,指定 proxy-target-class = "true" 或者 基于注解 @EnableAspectJAutoProxy(proxyTargetClass = true)
那么问题来了:JDK动态代理为什么要实现接口?
JDK 的动态代理是靠多态和反射来实现的,它生成的代理类需要实现你传入的接口,并通过反射来得到接口的方法对象,并将此方法对象传参给代理类的 invoke 方法去执行,从而实现了代理功能,故接口是 JDK 动态代理的核心实现方式,没有它就无法通过反射找到方法。那为什么不能通过继承实现呢?生成的代理类继承了 Proxy,由于 Java 是单继承,所以只能通过接口实现

JDK 动态代理

  • 实现步骤
      1. 代理类实现 InvocationHandler 接口,组合 Object(被代理类)并构造,然后织入,通过 method.invoke(Object target, Object[] args) 完成被代理类方法的调用
      1. 定义创建动态代理的方式(使用Proxy.newProxyInstance(Classloader loader, 类<?>[ ]… interfaces,InvocationHandler h)返回指定接口的代理类实例)
  • 编写 JDK 动态代理代码

cglib 动态代理

  • 实现步骤
      1. 代理类实现 MethodInterceptor 接口,然后织入,通过 methodProxy.invokeSuper(Object o, Object[] args) 完成被代理类方法的调用
      1. 定义创建动态代理的方式(使用 Enhancer.create(Class<?> target.getClass(), MethodInterceptor methodInterceptor)返回指定接口的代理类实例)
  • 编写 cglib 动态代理代码

区别

  • JDK 代理和 cglib 代理
使用 cglib 实现动态代理,cglib 底层采用ASM字节码生成框架,使用字节码技术生成代理类,在 JDK 1.6 之前比使用 Java 反射效率要高。唯一需要注意的是,cglib 不能对声明为final的类或者方法进行代理,因为 cglib 原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于 cglib 代理效率,只有当进行大量调用的时候,JDK1.6和 JDK1.7 比 cglib 代理效率低一点,但是到 JDK1.8 的时候,JDK代理效率高于 cglib 代理。所以如果有接口使用 JDK 动态代理,如果没有接口使用 cglib 代理。
  • 动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有 ( 需要增强功能的 ) 方法在使用静态代理时都要在原始类和代理类中重写,而动态代理会把这些接口转移到代理类一个集中的方法中处理(InvocationHandler#invoke)。这样,在接口方法数量比较多的时候不需要像静态代理那样每一个方法进行中转。还有一个好处是:如果要实现相同功能增强的类不止一个时,只需要创建一个代理类即可。
Loading...