定义
代理模式(Proxy Pattern)的定义非常简单,是指为其他对象提供一种代理,以控制对这个对象的访问。 代理对象在客户端和目标对象之间起到中介作用,代理模式属于结构型设计模式。
使用 代理模式主要有两个目的:一保护目标对象,二增强目标对象。下面我们来看一下代理模式的类结构图:
Subject
是顶层接口,RealSubject
是真实对象(被代理对象),Proxy
是代理对象,代理对象持有被代理对象的引用,客户端调用代理对象方法,但是在代理对象前后增加一些处理。在代码中,我们想到代理,就会理解为是代码增强。其实就是在原本逻辑前后增加一些逻辑,而调用者无感知。代理模式属于结构型 模式,有静态代理和动态代理。
静态代理
静态代理的实现比较简单,代理类通过实现与目标对象相同的接口,并在类中维护一个代理对象。通过构造器塞入目标对象,赋值给代理对象,进而执行代理对象实现的接口方法,并实现前拦截,后拦截等所需的业务功能。
1 | /** |
静态代理的总结:
- 优点:可以做到不对目标对象进行修改的前提下,对目标对象进行功能的扩展和拦截。
- 缺点:因为代理对象,需要实现与目标对象一样的接口,会导致代理类十分繁多,不易维护,同时一旦接口增加方法,则目标对象和代理类都需要维护。
动态代理
动态代理是指动态的在内存中构建代理对象(需要我们制定要代理的目标对象实现的接口类型),即利用JDK的API生成指定接口的对象,也称之为JDK代理或者接口代理。
1 | public class JdkDynamicTest { |
动态代理的总结:
- 优点:动态创建代理类,免去了编写很多代理类的烦恼,同时接口增加方法也无需再维护目标对象和代理对象,只需在事件处理器中添加对方法的判断即可。
- 缺点:目标对象一定要实现接口,否则无法使用JDK动态代理。
JDK动态代理实现
我们都知道 JDK Proxy 采用字节重组,重新生的对象来替代原始的对象以达到动态代理的目的。JDK Proxy 生成对象的步骤如下:
- 拿到被代理对象的引用,并且获取到它的所有的接口,反射获取。
- JDK Proxy 类重新生成一个新的类、同时新的类要实现被代理类所有实现的所有的接口。
- 动态生成 Java 代码,把新加的业务逻辑方法由一定的逻辑代码去调用(在代码中体现)。
- 编译新生成的 Java 代码.class。
- 再重新加载到 JVM 中运行。
以上这个过程就叫字节码重组。JDK 中有一个规范,在ClassPath
下只要是$
开头的class
文件一般都是自动生成的。我们可以从内存中的对象字节码通过文件流输出到一个新的class
文件,然后,利用 反编译工具查看class
的源代码。
$proxy.class
源码如下:
$Proxy0
继承了Proxy
类,同时还实现了我们的BusinessInterface
接口,而且重写了execute()
等方法。而且在静态块中用反射查找到了目标对象的所有方法,而且保存了所有方法的引用,在重写的方法用反射调用目标对象的方法。
手写JDK动态代理核心实现
cglib动态代理使用
1 | public class CglibDynamicTest { |
CGLib 代理的目标对象不需要实现任何接口,它是通过动态继承目标类实现的动态代理。
利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘:
1 | //利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘 |
cglib原理分析
代理类部分源码:
我们通过代理类的源码可以看到,代理类会获得所有在父类继承来的方法,并且会有MethodProxy
与之对应。代理类还会生成一个个直接指向父类进行调用的方法
调用过程:
- 代理对象用
this.execute()
方法。 - 调用拦截器
MethodInterceptor
的拦截方法。 methodProxy.invokeSuper
CGLIB$execute$0Proxy
调用被代理对象execute()
方法。
此时,我们发现拦截器MethodInterceptor
中就是由MethodProxy
的invokeSupe
方法调用代理方法的,MethodProxy
非常关键。
代理类部分源码:
MethodProxy源码:
CGLib 动态代理执行代理方法效率之所以比JDK的高是因为Cglib
采用了FastClass
机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class
,这个Class
会为代理类或被代理类的方法分配一个 index(int 类型)
。这个 index 当做一个入参,FastClass
就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK动态代理通过反射调用高。
总结
CGLib 和 JDK 动态代理对比
1.JDK 动态代理是实现了被代理对象的接口,CGLib 是继承了被代理对象。
2.JDK 和 CGLib 都是在运行期生成字节码,JDK 是直接写 Class 字节码,CGLib 使用 ASM框架写 Class 字节码,Cglib 代理实现更复杂,生成代理类比 JDK 效率低。
3.JDK 调用代理方法,是通过反射机制调用,CGLib 是通过 FastClass 机制直接调用方法,CGLib 执行效率更高。
Spring 中的代理选择原则
当 Bean 有实现接口时,Spring 就会用 JDK 的动态代理
当 Bean 没有实现接口时,Spring 选择 CGLib
Spring 可以通过配置强制使用 CGLib,只需在 Spring 的配置文件中加入如下代码:
1
2<aop:aspectj-autoproxy proxy-target-class="true"/>
`
静态代理和动态的本质区别
- 静态代理只能通过手动完成代理操作,如果被代理类增加新的方法,代理类需要同步新增,违背开闭原则。
- 动态代理采用在运行时动态生成代码的方式,取消了对被代理类的扩展限制,遵循开闭原则。
- 若动态代理要对目标类的增强逻辑扩展,结合策略模式,只需要新增策略类便可完成,无需修改代理类的代码。
代理模式的优缺点
使用代理模式具有以下几个优点:
- 代理模式能将代理对象与真实被调用的目标对象分离。
- 一定程度上降低了系统的耦合度,扩展性好。
- 可以起到保护目标对象的作用。
- 可以对目标对象的功能增强。
当然,代理模式也是有缺点的:
- 代理模式会造成系统设计中类的数量增加。
- 在客户端和目标对象增加一个代理对象,会造成请求处理速度变慢。
- 增加了系统的复杂度。