浅谈 Spring AOP 原理

image.png

AOP 底层是采用 动态代理 机制实现的:接口+实现类

  • 如果要代理的对象,实现了某个接口,那么 Spring AOP 会使用 JDK Proxy ,去创建代理对象。
  • 没有实现接口的对象,就无法使用 JDK Proxy 去进行代理了,这时候 Spring AOP 会使用 Cglib 生成一个被代理对象的子类来作为代理。

就是由代理创建出一个和 impl 实现类平级的一个对象,但是这个对象不是一个真正的对象,只是一个代理对象,但它可以实现和 impl 相同的功能,这个就是 aop 的横向机制原理,这样就不需要修改源代码。

AOP 思想: 基于代理思想,对原来目标对象,创建代理对象,在不修改原对象代码情况下,通过代理对象,调用增强功能的代码,从而对原有业务方法进行增强 !

Spring AOP流程图.png

应用场景:

  1. 记录日志
  2. 监控方法运行时间(监控性能)
  3. 权限控制
  4. 缓存优化
  5. 事务管理

JDK 动态代理

JDK中的动态代理是通过反射类Proxy以及InvocationHandler回调接口实现的,但是JDK中所有要进行动态代理的类必须要实现一个接口,也就是说只能对该类所实现接口中定义的方法进行代理,这在实际编程中有一定的局限性,而且使用反射的效率也不高。

背景:

动态代理是为了解决静态代理的缺点:

  1. 当目标类增加了,代理类可能也需要成倍的增加,代理类数量过多。
  2. 当你的接口中功能增加了,或者修改了,会影响众多的实现类,厂家类,代理都需要修改,影响比较多。

JDK 动态代理中目标类即使很多:代理类数量可以很少,当你修改了接口中的方法时,不会影响代理类。

原理:

利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用 InvokeHandler 来处理。

JDK 动态代理为什么必须实现接口?

  1. 在需要继承proxy类获得有关方法和InvocationHandler构造方法传参的同时, java不能同时继承两个类,我们需要和想要代理的类建立联系,只能实现一个接口。
  2. 需要反射获得代理类的有关参数,必须要通过某个类,反射获取有关方法。
  3. 成功返回的是object类型,要获取原类,只能继承/实现,或者就是那个代理类。
  4. 对具体实现的方法内部并不关心,这个交给InvocationHandler.invoke那个方法里去处理就好了,我只想根据你给我的接口反射出对我有用的东西。
  5. 考虑到设计模式,以及proxy编者编写代码的逻辑使然。

CGLib 动态代理

CGLib 是一个功能强大,高性能的代码生成包。 它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。 通常可以使用Java的动态代理创建代理,但当要代理的类没有实现接口或者为了更好的性能,CGLIB是一个好的选择。

背景:

CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?

答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是, 只能对接口进行代理。 如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。

原理:

动态生成一个要代理类的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。它比使用java反射的JDK动态代理要快。

缺点:

对于final方法,无法进行代理。

应用场景:

  • 回调过滤器CallbackFilter:在CGLib回调时可以设置对不同方法执行不同的回调逻辑,或者根本不执行回调。
  • 延迟加载对象
  • 接口生成器 InterfaceMaker:InterfaceMaker会动态生成一个接口,该接口包含指定类定义的所有方法。

JDK 动态代理 VS CGLib 动态代理

区别:

  • Jdk动态代理:利用拦截器(必须实现InvocationHandler)加上反射机制生成一个代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
  • Cglib动态代理:利用 ASM 框架,对代理对象类生成的class文件加载进来,通过修改其字节码生成子类来处理。

怎么选择?

  1. 目标对象生成了接口 默认用JDK动态代理
  2. 如果目标对象使用了接口,可以强制使用cglib
  3. 如果目标对象没有实现接口,必须采用cglib库,Spring会自动在JDK动态代理和cglib之间转换

Spring如何选择是用JDK还是cglib?

1、当bean实现接口时,会用JDK代理模式
2、当bean没有实现接口,用cglib实现
3、可以强制使用cglib(在spring配置中加入<aop:aspectj-autoproxy proxyt-target-class=”true”/>)