目录

Spring Boot AOP

Aspect Oriented Programming

AOP (Aspect-Oriented Programming)是一种通过允许跨领域关注点分离来提高模块化的编程模式。

AOP 在实际项目中运用的场景主要有 权限管理(Authority Management)、事务管理(Transaction Management)、安全管理(Security)、日志管理(Logging)和调试管理(Debugging) 等。

一些适用于所有层的常见方面是 日志记录,安全性,验证,缓存等。这些常见方面被称为 跨领域关注点。 为了克服这个问题, 面向方面的编程(AOP)提供了一种解决跨领域问题的解决方案。

  • 将跨领域关注作为一个方面。

  • 定义切入点以指示必须在何处应用方面。 它确保跨领域关注点在一个内聚的代码组件中定义。

  • 首先,每个关注点的逻辑现在都集中在一个地方,而不是分散在整个代码库中。 — 其次,业务模块仅包含主要关注的代码。次要关注点已移至方面。 有两种类型的AOP代理: JDK动态代理和 CGLIB代理。

AOP

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

原理

一是采用动态代理技术,利用截取消息的方式,对该消息进行装饰,以取代原有对象行为的执行;

二是采用静态织入的方式,引入特定的语法创建“方面”,从而使得编译器可以在编译期间织入有关“方面”的代码。

Spring AOP 的实现原理其实很简单:AOP 框架负责动态地生成 AOP 代理类,这个代理类的方法则由 Advice 和回调目标对象的方法所组成,并将该对象可作为目标对象使用。AOP 代理包含了目标对象的全部方法,但 AOP 代理中的方法与目标对象的方法存在差异,AOP 方法在特定切入点添加了增强处理,并回调了目标对象的方法。

Spring AOP使用动态代理技术在运行期织入增强代码。使用两种代理机制:

基于JDK的动态代理(JDK本身只提供接口的代理);

基于CGlib的动态代理

1)JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler只是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑与业务逻辑织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。 其代理对象必须是某个接口的实现,它是通过在运行期间创建一个接口的实现类来完成对目标对象的代理.只能实现接口的类生成代理,而不能针对类

2)CGLib采用底层的字节码技术,为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类的调用方法,并顺势织入横切逻辑.它运行期间生成的代理对象是目标类的扩展子类.所以无法通知final的方法,因为它们不能被覆写.是针对类实现代理,主要是为指定的类生成一个子类,覆盖其中方法.

在spring中默认情况下使用JDK动态代理实现AOP,如果proxy-target-class设置为true或者使用了优化策略那么会使用CGLIB来创建动态代理.Spring AOP在这两种方式的实现上基本一样.以JDK代理为例,会使用JdkDynamicAopProxy来创建代理,在invoke()方法首先需要织入到当前类的增强器封装到拦截器链中,然后递归的调用这些拦截器完成功能的织入.最终返回代理对象

AOP 术语

  • Aspect: 方面是一个模块,其中封装了advice和pointcuts,并提供cross-cutting可以有许多方面。我们可以使用带有 @Aspect 批注的常规类来实现方面。
  • Pointcut: 切入点是一种表达式,它选择一个或多个执行Advice的连接点。我们可以使用expressions或patterns定义切入点。它使用与联接点匹配的不同类型的表达式。在Spring Framework中,使用 AspectJ 切入点表达语言。
  • Join point: 连接点是应用程序中应用 AOP方面的点。或者它是Advice的特定执行实例。在AOP中,连接点可以是方法执行,异常处理,更改对象变量值等。
  • Advice: Advice是我们在方法执行之前before或after采取的措施。该动作是在程序执行期间调用的一段代码。SpringAOP框架中有五种类型的Advice: 在Advicebefore, after, after-returning, after-throwing和around advice。 是针对特定join point的Advice。 我们将在本节中进一步讨论这些Advice。
  • Target object: 一个应用了Advice的对象称为target object。目标对象始终是proxied,这意味着在运行时将创建一个覆盖目标方法的子类,并根据其配置包含Advice。
  • Weaving: 这是将各个方面与其他应用程序类型进行linking aspects的过程。我们可以在运行时,加载时间和编译时进行织造。
  • Proxy: 它是在将Advice应用于目标对象后创建的对象,称为 proxy 。 Spring AOP实现了 JDK动态代理,以使用目标类和Advice调用创建代理类。这些称为AOP代理类。

Aspect 切面

AOP 核心就是切面,它将多个类的通用行为封装成可重用的模块,该模块含有一组API提供横切功能。比如,一个日志模块可以被称作日志的AOP切面。根据需求的不同,一个应用程序可以有若干切面。在Spring AOP中,切面通过带有 @Aspect 注解的类实现。事务管理是J2EE应用中一个很好的横切关注点例子。

Joinpoint 连接点

Joinpoint是程序执行过程中的一个点,例如方法的执行或异常的处理。

在 Spring AOP 中,一个 JoinPoint 总是代表一个方法执行。

Pointcut 切点

切入点是一个谓词,有助于匹配要由 Aspect 在特定 JoinPoint 应用的Advice。 经常将Advice与Pointcut表达式相关联,它在任何与Pointcut匹配的Joinpoint处运行。

Advice 通知

Advice 是 Aspect 在特定 Joinpoint 处采取的操作。不同的 advice 包括 around,before,及 after

在 Spring 中,一个Advice被建模为一个拦截器,在Joinpoint周围维护一个拦截器链。

  • @Before Before Advice: 在连接点之前执行的Advice在通知之前被调用。
  • @After After Advice: 在连接点之后执行的Advice被称为after notification。
  • @Around Around Advice: 在连接点之前和之后执行的Advice
  • @AfterReturning After Returning: 当方法成功执行时执行的Advice。
  • @AfterThrowing After Throwing: 在连接点抛出异常时执行的Advice。

Introduction 引入

引入允许我们在已存在的类中增加新的方法和属性。添加方法或字段到被通知的类。 Spring允许引入新的接口到任何被通知的对象。

Target Object 目标对象

被一个或者多个切面所通知的对象。它通常是一个代理对象。也指被通知(advised)对象。

AOP代理(AOP Proxy)

代理是通知目标对象后创建的对象。从客户端的角度看,代理对象和目标对象是一样的。AOP框架创建的对象,包含通知。 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。

织入(Weaving)

组装方面来创建一个被通知对象。这可以在编译时完成(例如使用AspectJ编译器),也可以在运行时完成。Spring和其他纯Java AOP框架一样,在运行时完成织入。

织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。

织入可以在编译时,加载时,或运行时完成。

Spring AOP

虽然spring aop采用了aspectj语法来定义切面,但是在实现切面逻辑的时候还是采用CGLIB来进行动态代理的方法。

1、基于动态代理来实现,默认如果使用接口的,用JDK提供的动态代理实现,如果是方法则使用CGLIB实现

2、Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现

3、在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如AspectJ的那么好

AspectJ

AspectJ是一个易用的功能强大的AOP框架

AspectJ全称是Eclipse AspectJ, 其官网地址是:http://www.eclipse.org/aspectj/

  • a seamless aspect-oriented extension to the Javatm programming language(一种基于Java平台的面向切面编程的语言)
  • Java platform compatible(兼容Java平台,可以无缝扩展)
  • easy to learn and use(易学易用)

可以单独使用,也可以整合到其它框架中。

单独使用AspectJ时需要使用专门的编译器ajc。

java的编译器是javac,AspectJ的编译器是ajc,aj是首字母缩写,c即compiler。

  • AspectJ来自于Eclipse基金会
  • AspectJ属于静态织入,通过修改代码来实现,有如下几个织入的时机:

1、编译期织入(Compile-time weaving): 如类 A 使用 AspectJ 添加了一个属性,类 B 引用了它,这个场景就需要编译期的时候就进行织入,否则没法编译类 B。

2、编译后织入(Post-compile weaving): 也就是已经生成了 .class 文件,或已经打成 jar 包了,这种情况我们需要增强处理的话,就要用到编译后织入。

3、类加载后织入(Load-time weaving): 指的是在加载类的时候进行织入,要实现这个时期的织入,有几种常见的方法。1、自定义类加载器来干这个,这个应该是最容易想到的办法,在被织入类加载到 JVM 前去对它进行加载,这样就可以在加载的时候定义行为了。2、在 JVM 启动的时候指定 AspectJ 提供的 agent:-javaagent:xxx/xxx/aspectjweaver.jar

  • AspectJ可以做Spring AOP干不了的事情,它是AOP编程的完全解决方案,Spring AOP则致力于解决企业级开发中最普遍的AOP(方法织入)。而不是成为像AspectJ一样的AOP方案
  • 因为AspectJ在实际运行之前就完成了织入,所以说它生成的类是没有额外运行时开销的

Spring AOP 和 AspectJ

能力和目标

Spring AOP 旨在跨 Spring IoC 提供一个简单的 AOP 实现,以解决程序员面临的最常见问题。它并不是一个完整的 AOP 解决方案——它只能应用于由 Spring 容器管理的 bean。

另一方面,AspectJ 是独创的 AOP 技术,旨在提供完整的 AOP 解决方案。它比 Spring AOP 更健壮但也更复杂。还值得注意的是,AspectJ 可以应用于所有域对象。

编织

AspectJ 和 Spring AOP 都使用不同类型的编织,这会影响它们在性能和易用性方面的行为。

AspectJ 使用三种不同类型的编织:

  • 编译时编织:AspectJ 编译器将方面的源代码和应用程​​序的源代码都作为输入,并生成编织的类文件作为输出
  • 编译后编织:这也称为二进制编织。它用于将现有的类文件和 JAR 文件与我们的切面编织在一起
  • 加载时编织:这与之前的二进制编织完全相同,不同之处在于编织被推迟到类加载器将类文件加载到 JVM 之后

由于 AspectJ 使用编译时和类加载时编织,Spring AOP 使用运行时编织。

使用运行时编织,方面在应用程序执行期间使用目标对象的代理进行编织——使用 JDK 动态代理或 CGLIB 代理

内部结构及应用

Spring AOP 是一个基于代理的 AOP 框架。这意味着要实现目标对象的方面,它将创建该对象的代理。这是通过以下两种方式之一实现的:

  • JDK 动态代理——Spring AOP 的首选方式。只要目标对象实现了一个接口,就会使用JDK动态代理
  • CGLIB 代理——如果目标对象没有实现接口,则可以使用 CGLIB 代理

另一方面,AspectJ 在运行时不做任何事情,因为类是直接用方面编译的。

因此与 Spring AOP 不同,它不需要任何设计模式。为了将方面编织到代码中,它引入了称为 AspectJ 编译器 (ajc) 的编译器,通过它我们编译我们的程序,然后通过提供一个小型 (< 100K) 运行时库来运行它。

连接点

Spring AOP 只支持方法执行的连接点。

AspectJ 支持 方法调用、方法执行、构造函数调用、构造函数执行、静态初始化程序执行、对象初始化、字段参考、 现场分配、 处理程序执行、 建议执行。

Spring AOP 和 AspectJ 之间的关键区别:

Spring AOP AspectJ
在纯 Java 中实现 使用 Java 编程语言的扩展实现
不需要单独的编译过程 除非设置 LTW,否则需要 AspectJ 编译器 (ajc)
只能使用运行时织入 运行时织入不可用。支持编译时、编译后和加载时织入
功能不强-仅支持方法级编织 更强大 - 可以编织字段、方法、构造函数、静态初始值设定项、最终类/方法等……。
只能在由 Spring 容器管理的 bean 上实现 可以在所有域对象上实现
仅支持方法执行切入点 支持所有切入点
代理是由目标对象创建的, 并且切面应用在这些代理上 在执行应用程序之前 (在运行时) 前, 各方面直接在代码中进行织入
比 AspectJ 慢多了 更好的性能
易于学习和应用 相对于 Spring AOP 来说更复杂

虽然 Spring AOP 采用了 Aspectj 语法来定义切面,但是在实现切面逻辑的时候还是采用CGLIB来进行动态代理的方法。

如何选择

  • 框架:如果应用程序没有使用 Spring 框架,那么我们别无选择,只能放弃使用 Spring AOP 的想法,因为它无法管理任何超出 Spring 容器范围的东西。但是,如果我们的应用程序完全使用 Spring 框架创建,那么我们可以使用 Spring AOP,因为它易于学习和应用
  • 灵活性:鉴于有限的连接点支持,Spring AOP 不是一个完整的 AOP 解决方案,但它解决了程序员面临的最常见问题。虽然如果我们想深入挖掘并最大限度地利用 AOP 并希望获得各种可用连接点的支持,那么 AspectJ 是不二之选
  • 性能:如果我们使用有限的方面,那么性能差异很小。但有时应用程序具有数以万计的方面。我们不想在这种情况下使用运行时编织,所以最好选择 AspectJ。众所周知,AspectJ 比 Spring AOP 快 8 到 35 倍
  • 两全其美:这两个框架彼此完全兼容。我们总是可以尽可能利用 Spring AOP 并且仍然使用 AspectJ 来支持前者不支持的连接点

https://www.baeldung.com/spring-aop-vs-aspectj

https://www.baeldung.com/aspectj

https://blog.mythsman.com/post/5d301cf2976abc05b34546be/

https://www.liaoxuefeng.com/wiki/1252599548343744/1310052352786466

https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-pointcuts-examples

https://blog.csdn.net/hosaos/article/details/102931887