Spring AOP 的设计与实现

发布于:2025-03-27 ⋅ 阅读:(49) ⋅ 点赞:(0)

AOP,Aspect Oriented Programming面向切面编程,通过横向切割的方式将通用逻辑(如日志、事务、权限校验等)插入到业务逻辑中,并在编码上与业务逻辑代码解耦。

1 概述

连接点

Join Point,需要被AOP插入通用逻辑的代码位置。

切点

Pointcut,通过表达式来匹配(挑选)连接点。

通知

Advice,通用逻辑,即AOP要对连接点插入的增强代码片段。

切面

Aspect,由切点和通知组成。

表 AOP的相关概念

织入:在连接点插入通知的过程。

引入:给类添加新的方法或字段。

1.1 AspectJ

是面向切面的一个框架,通过扩展Java语言及专门的编译器来实现AOP。支持字段、构造器、静态块等AOP,而Spring的AOP只支持方法级别的切面。Spring 的AOP底层由动态代理实现。

2 Spring AOP 的基础用法

@EnableAspectJAutoProxy 是Spring 框架中用于启用AspectJ自动代理的注解。添加后,Spring容器会自动为被@Aspect标注的代理类创建代理,并将切面逻辑织入到目标Bean中。

注意:切面需要是一个bean才会被容器扫描到,切面的切点不容作用于其他切面的Advice。

2.1 切点

只支持方法运行的连接点。(而AspectJ还支持方法调用call的连接点)

execution

匹配方法。

execution(修饰符 返回类型 包.类.方法(参数) 异常)

within

连接点的结构位置。(包、类)

with(包.类名) 包和类名都支持通配符

this

限定代理器的类型

this(类型全限定名)

target

限定目标类的类型

this(类型全限定名)

args

方法的参数

args(参数类型列表)

@target

运行时检查目标对象的类注解。

@args

匹配方法参数上的注解。

@within

匹配类上的注解

@annotation

匹配方法或类上的注解。

表 Spring AOP的切点指示器

多个指示器可通过逻辑运算符组合在一起。

2.1.1 代理器与目标类的类型

Spring Boot 2.xx版本开始指定AOP的代理实现方式为CGLIB动态代理。如果关闭,则当目标bean有接口时采用JDK动态代理,否则采用CGLIB动态代理。

当采用JDK动态代理时,代理器会实现目标bean的所有接口。

当采用CGLIB动态代理时,代理器会继承目标bean。

2.2 通知

@Before

在连接点之前运行。

@After

无论正常还是异常退出,都会执行。

@AfterReturning

在连接点正常完成后运行。

@AfterThrowing

如果方法抛出异常而退出,则执行。

@Around

围绕连接点。还负责选择继续执行到连接点,还是通过返回自己的返回值或抛出异常终止执行。

表 Spring AOP支持的通知类型

2.3 切面

默认情况下,只会为每个切面创建一个实例。@Aspect注解可以通过设置pertarget及perthis来配置切面生成专属实例的动作。

@Aspect(“perthis(表达式)”):当代理对象满足表达式时,为该代理对象创建一个专属的切面实例。

@Aspect(“pertarget(表达式)”):当目标对象满足切点表达式时,为该目标对象创建一个专属的切面实例。

2.4 引入

@DeclareParents注解作用于接口类型的字段上,该接口是需要为目标类型引入的接口。该注解有两个属性:value,指定目标类型的表达式。defaultImpl 指定引入接口的实现类。

注意:@DeclareParents需要在切面中才会生效。

3 Spring AOP的底层实现

aop 的实现步骤为:1)切面配置。2)切面解析与注册。3)代理对象生成。

3.1 切面解析与注册

当添加@EnableAspectJAutoProxy注解时,容器会按以下步骤对切面进行解析与注册:

  1. 扫描所有带有@Aspect注解的切面bean。
  2. 解析切面中@Pointcut及@Before等通知注解,生成Advisor对象。
  3. 将Advisor注册到AdvisedSupport中,供后续使用。

3.1.1 解析内容

1)提取@Pointcut的定义并生成AspectJExpressionPointcut对象。

2)解析Advice注解,针对每个通知方法,生成对应的Advice对象,并与关联的切点绑定。

3)构建Advisor对象,将上面解析Pointcut和Advice组合成Advisor。

注意引入Introduction也是一种Advice。DeclareParentsAdvisor用于实现@DeclareParents注解的引入操作。

3.2 代理对象生成

容器会检查每个bean(除基础bean,比如切面bean)是否能和容器中的Advisor匹配,如果至少存在一个匹配,那么会调用ProxyFactory来创建代理对象。

3.1.1 判断及生成代理时机

1、不存在循环依赖的bean

bean初始化完成后,会调用BeanPostProcessor接口的postProcessAfterInitialization方法,容器会在这个方法中来判断及创建代理bean。

2、存在循环依赖的bean

三级缓存结构的第三级存储了该bean的创建工厂对象,当需要提取暴露bean的引用时,容器会将这个工程对象取出并调用getObject()方法,容器会在这个方法中来判断及创建代理bean。

3.1.2 判断是否需要创建代理

判断这个bean是否需要创建代理,会遍历容器所有的Advisor对象,检查该对象的Pointcut是否匹配当前bean的类和方法。

匹配类级别:ClassFilter

匹配方法级别:MethodMacher

静态切点

基于静态信息匹配,只需匹配一次。

动态切点

依赖运行时的信息(方法参数值、对象实际类型等),在每次方法调用时动态判断。

表 Pointcut的静态与动态切点

3.1.3 生成代理

如果有一个Advisor匹配这个bean,那么就会生成代理对象。如果有多个Advisor匹配,会根据通知的指示器类型(Before、After、Around等)放到不同的调用链上。Advised 组件负责关联对象的通知链和配置。

AopProxy 定义了代理对象的创建方法,JdkDynamicAopProxy 和CglibAopProxy是其实现类,分别定义了JDK动态代理及CGLIB动态代理创建代理对象的方法。