聊一聊SpringAop

最近重温了一下《Spring 源码深度解析》,从学习相关知识以来越发觉得 Aop(切面编程) 的好用。于是简单做一下笔记记录以及资料收集。

聊一聊 Spring AOP

什么是 AOP ?

AOP,面向切面编程,通过预编译和运行期动态代理实现程序功能的统一维护的一种技术。AOP,是 Spring 框架中一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 —— 来源于网络

通常情况下,我们都是使用 框架自动创建AOP代理,它可以分为静态代理和动态代理。在Spring中,静态代理采用了AspectJ,而动态代理根据应用场景选用了 JDK动态代理 或者 CGLIB动态代理。(代理部分知识详见 Java中的代理)

使用 AspectJ 实现 AOP

首先,要明白的一点 : AspectJ 采用了静态织入,就是在编译期就织入(编译出来的 class 文件,字节码就已经织入)。关于如何在 Spring 中采用静态 AOP 的资料网上有许多,也并没有什么特别之处,所以笔记就不再啰嗦这一部分。(具体 Google 或者 Baidu)

知识碎片

  • 在传统的 Spring 项目(基于 XML 配置)中,我们是采用 <aop:aspectj-autoproxy/>来启用 AOP;而在 SpringBoot 项目中,则是采用注解@EnableAspectJAutoProxy 来开启AOP。
  • JDK 1.5 引入了 java.lang.instrument ,提供给使用者来实现一个 Java agent,并通过 agent 来修改类的字节码。(补充,什么是 Java agent ?就是运行在 main 方法之前的拦截器)
  • 在 Spring 中静态 AOP 直接使用了 AspectJ 提供的方法,而 AspectJ 是基于 Instrument 基础上进行的封装。
  • 举个栗子,假如我们对一个方法使用AspectJ的编译增强实现 AOP : Aspect 会自动将我们调用该方法的语句转为调用到我们指定的 AOP 实现类,并在实现类中回调我们真正想调用的方法(基本思路就是 : 替换 -> 调用AOP类 -> 回调目标对象)。

编译期织入(图片来源于网络)

动态代理实现 AOP

Spring 中动态 AOP 采用了 JDK动态代理 和 CGLIB动态代理。所谓的 动态AOP 其实就是指框架本身并不去修改字节码,而是临时为方法生成一个 AOP 对象(包含了目标对象所有的同名方法),该对象在特定的切点进行回调目标对象的真实方法。

Spring AOP 如何实现动态代理?

  1. 完成所有 Bean 的加载,并遍历所有 beanName 提取出声明 AspectJ 注解的类。
  2. 对提取出来是类进行增强器寻找。
    • 首先是获取切点信息(就是类似于 @Before 之类的注解)
    • 根据不同的注解生成不同的增强器。
    • 创建代理对象。
  3. 将结果加入到缓存中以方便调用。

综上,Spring AOP 框架所做的任务就是根据目标对象构造各类增强器,并组成拦截器链,但真正利用代理模式调用到目标对象之时依照切点信息执行拦截器链上的对象(方法)。关于这部分建议仔细阅读《Spring 源码深度解析》。

Spring 动态代理策略

  • 如果目标对象实现了接口,默认情况下会采用 JDK 动态代理实现 AOP。
  • 如果目标对象实现了接口,可以强制使用 CGLIB 实现 AOP。
  • 如果目标对象没有实现接口,必须采用 CGLIB 库,Spring 会自动在 JDK 动态代理和 CGLIB 之间转换。

(补充 : JDK 动态代理只能针对接口的类生成代理,在代理类中调用业务实现类的同名方法; CGLIB 是针对类实现代理,对指定的类生成子类并覆盖其中的方法,是一种继承的关系)

AOP 代理备注

  • 效率上,静态代理相对高效于动态代理。

    静态代理是在虚拟机启动时通过改变目标对象的字节码来完成对目标对象的增强;而动态代理在调用过程中,还需要动态创建代理类并代理目标对象。

    由此,当采用静态代理时,系统给再次调用目标类和调用正常的类并无差别。

  • 两种动态代理的区别:

    • JDK 动态代理:代理对象必须是某个接口的实现,通过在运行期间创建一个接口的实现类来完成对目标对象的代理。
    • CGLIB 代理:原理类似前者,只是在运行期间生成了针对于目标对象扩展的子类,其底层通过了 ASM(来源 Java 字节码编辑类库) 操作字节码来实现,其性能比 JDK 动态代理强。
评论