【Spring Boot】Spring AOP动态代理,以及静态代理
目录
- Spring AOP代理
- 一. 代理的概念
- 二. 静态代理
- 三. JDK代理
- 3.1 重写 invoke 方法进⾏功能增强
- 3.2 通过Proxy类随机生成代理对象
- 四. CGLIB代理
- 4.1 自定义类来重写intercept方法
- 4.2 通过Enhancer类的create方法来创建代理类
- 五. AOP源码剖析
- 总结(重中之重,精华)
Spring AOP代理
一. 代理的概念
根据前面的学习想必大家都已经对Spring AOP有所了解了,接下来我们先来回忆一下什么是Spring AOP?
AOP:一种对于集中的事情进行统一处理解决的思想;
Spring AOP:Spring通过运用AOP统一解决的思想所诞生的产物;
例如:拦截器,适配器,统一结果返回,统一异常处理,以及统一通知处理,以上这些在我们前面的文章中都讲述过,已经有些遗忘的小伙伴可以翻看前面的文章进行稳固一下…
好!接下来我们进入正题…
AOP的底层原理实现的代理模式,那么什么是代理模式呢?
通过举一个栗子~大家就应该能够了解了:有些 小伙伴可能通过一些线上平台租过房子,那么这个线上平台就是我们说的中介,是在我们跟房子房东之间的纽带,为啥我们要找中介呢?
- 中介能够帮我们提前去验收要出租房子的质量来进行出租价格的评定
- 中介能够在我们住房期间能够对房子的进行一个改造升级
通过上面的栗子中的 中介就是代理,我们通过中介能够达到我们最终的要求,这就是代理模式,简单来说就是通过一个代理类能够间接的调用目标方法。
然而代理模式又分为两种:
- 静态代理
- 动态代理(两种)
静态代理和动态代理的主要区别就是:静态代理的代理对象的一开始就定好的,而动态代理就跟他的命名一样,是动态化的,是由系统随机调度生成的一个代理对象
按照上面的例子来说就是,静态代理A的房子,那么A房子的中介人一直是这个人,而动态代理是中介公司看现在哪一个中介在摸鱼,就让哪一个中介去干活~
当然了,在面试中主要考查的是动态代理;
动态代理(主要是通过反射来完成的代理模式):
- JDK代理
- CGLIB代理
在接下来的讲解中我们将围绕以上几种代理进行展开,由于JDK代理和CGLIB代理是面试中的重中之重,篇幅较长,我们后面慢慢讲述,先就简单的,软的柿子——静态代理来捏~~
二. 静态代理
静态代理:由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译,在程序运⾏前代理类的
.class⽂件就已经存在了
什么意思呢?简单来理解就是它的代理对象已经定死了,不会在修改了。
代理(中介,帮房东出租房⼦)
public class HouseProxy implements HouseSubject{ //将被代理对象声明为成员变量 private HouseSubject houseSubject; public HouseProxy(HouseSubject houseSubject) { this.houseSubject = houseSubject; } @Override public void rentHouse() { //开始代理 System.out.println("我是中介, 开始代理"); //代理房东出租房⼦ houseSubject.rentHouse(); //代理结束 System.out.println("我是中介, 代理结束"); } }
这段代码不需要看明白,只用记住,代理对象通过renHouse方法来加强房子的质量,以及代理对象写死了就行了,重点全在动态代理中
三. JDK代理
在上面讲过,动态代理于静态代理的区别在于动态代理的代理对象是系统生成的,而JDK动态代理是通过:
JDK动态代理有⼀个最致命的问题是其只能代理实现了接口的类
- 通过实现InvocationHandler接口,重写 invoke 方法来加强被代理的对象(加强房子的质量)
- 通过Proxy类来随机生成一个代理对象
3.1 重写 invoke 方法进⾏功能增强
实现InvocationHandler接口,重写的 invoke 方法的伪代码如下,了解他们的区别和调用的方法即可:
public interface InvocationHandler { /** * 参数说明 * proxy:代理对象 * method:代理对象需要实现的方法,即其中需要重写的方法 * args:method所对应方法的参数 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
InvocationHandler接口是Java动态代理的关键接口之⼀,它定义了⼀个单⼀方法 invoke() ,⽤于
处理被代理对象的方法调⽤.通过实现 InvocationHandler 接口,可以对被代理对象的方法进⾏功能增强.
3.2 通过Proxy类随机生成代理对象
通过Proxy类来随机生成一个代理对象伪代码如下:
public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) throws IllegalArgumentException { //...代码省略 }
Proxy 类中使⽤频率最⾼的方法是: newProxyInstance() ,这个方法主要⽤来⽣成⼀个代理
对象
这个方法⼀共有3个参数:
Loader:类加载器,⽤于加载代理对象.
interfaces:被代理类实现的⼀些接口(这个参数的定义,也决定了JDK动态代理只能代理实现了接口的
⼀些类)
h:实现了InvocationHandler接口的对象
⾃定义 InvocationHandler 并重写 invoke 方法,在 invoke 方法中我们会调⽤⽬标方
法(被代理类的方法)并⾃定义⼀些处理逻辑
3. 通过 Proxy.newProxyInstance(ClassLoader loader,Class[]
interfaces,InvocationHandler h) 方法创建代理对象
四. CGLIB代理
JDK动态代理有⼀个最致命的问题是其只能代理实现了接口的类,而CGLIB却能够实现接口也能代理类
- 通过⾃定义 MethodInterceptor 并重写 intercept 方法加强被代理的对象(加强房子的质量)
- 通过Enhancer类的create()创建代理类
和JDK动态代理不同,CGLIB(CodeGenerationLibrary)实际是属于⼀个开源项⽬,如果你要使⽤它
的话,需要⼿动添加相关依赖
cglib cglib 3.3.0
4.1 自定义类来重写intercept方法
⾃定义 MethodInterceptor 并重写 intercept 方法, intercept ⽤于增强⽬标方法,和JDK动态代理中的 invoke 方法类似
代码如下:
import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CGLIBInterceptor implements MethodInterceptor { //⽬标对象, 即被代理对象 private Object target; public CGLIBInterceptor(Object target){ this.target = target; } @Override public Object intercept(Object o, Method method, Object[] objects,MethodProxy methodProxy) throws Throwable { // 代理增强内容 System.out.println("我是中介, 开始代理"); //通过反射调⽤被代理类的方法 Object retVal = methodProxy.invoke(target, objects); //代理增强内容 System.out.println("我是中介, 代理结束"); return retVal; } }
MethodInterceptor 和JDK动态代理中的 InvocationHandler 类似,它只定义了⼀个方
法 intercept() ,⽤于增强⽬标方法
4.2 通过Enhancer类的create方法来创建代理类
通过Enhancer类的create()创建代理类
伪代码如下:
public static Object create(Class type, Callback callback) { //...代码省略 }
参数说明:
type:被代理类的类型(类或接口)
callback:⾃定义方法拦截器MethodInterceptor
五. AOP源码剖析
SpringAOP主要基于两种方式实现的:JDK及CGLIB的方式
然而为什么会有两种不同的代理方式?以及什么时候用不同的代码方式呢?
在下面的部分源码中我们可以了解到
//创建代理⼯⼚ ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); /** * 检查proxyTargetClass属性值,spring默认为false * proxyTargetClass 检查接口是否对类代理, ⽽不是对接口代理 * 如果代理对象为类, 设置为true, 使⽤cglib代理 */ if (!proxyFactory.isProxyTargetClass()) { //是否有设置cglib代理 if (shouldProxyTargetClass(beanClass, beanName)) { //设置proxyTargetClass为true,使⽤cglib代理 proxyFactory.setProxyTargetClass(true); } else { /** * 如果beanClass实现了接口,且接口⾄少有⼀个⾃定义方法,则使⽤JDK代理 * 否则CGLIB代理(设置ProxyTargetClass为true ) * 即使我们配置了proxyTargetClass=false, 经过这⾥的⼀些判断还是可能会将其 设为true */ evaluateProxyInterfaces(beanClass, proxyFactory); } }
从上面的部分源码来看,接下来是我自己用大白话总结出来的道理,
首先会创建一个代理工厂,用于创建两种不同的代理对象,然后源码中有一个proxyTagertClass属性来进行判断,如果proxyTagertClass为false,且实现了接口,则用代理工厂来创建一个JDK代理的对象,如果没有实现接口或者proxyTagertClass为true,则用代理工厂来创建一个CGLIB代理的对象
根据proxyTagertClass属性来创建代理对象的情况如下表格:
proxyTargetClass ⽬标对象 代理方式 false 实现了接口 jdk代理 false 未实现接口(只有实现类) cglib代理 true 实现了接口 cglib代理 true 未实现接口(只有实现类) cglib代理 注意:这里的版本不同,创建的方式也会不同,在Spring版本中proxyTagertClass属性默认的为false,然而在Spring Boot 2.0版本以后proxyTagertClass属性则默认的是true ,也就是默认使用CGLIB来代理 总结(重中之重,精华)
两大重要的动态代理:
- JDK动态代理
- CGLIB动态代理
以及它两个的区别:
- JDK代理主要用于实现接口,不能直接用于类;而CGLIB在实现接口和类上都可以使用
- JDK代理通过重写invoke方法来进行功能增强,通过proxy类来生成随机代理对象,而CGLIB代理通过自定义类来重写intercept方法,通过Enhancer类的create方法来创建代理类
从动态代理的部分源码来看,接下来是我自己用大白话总结出来的过程,
首先会创建一个代理工厂,用于创建两种不同的代理对象,然后源码中有一个proxyTagertClass属性来进行判断,如果proxyTagertClass为false,且实现了接口,则用代理工厂来创建一个JDK代理的对象,如果没有实现接口或者proxyTagertClass为true,则用代理工厂来创建一个CGLIB代理的对象
以及proxyTagertClass属性在Spring Boot 2.0版本之前默认是false,而在2.0版本以后默认为true