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注解时,容器会按以下步骤对切面进行解析与注册:
- 扫描所有带有@Aspect注解的切面bean。
- 解析切面中@Pointcut及@Before等通知注解,生成Advisor对象。
- 将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动态代理创建代理对象的方法。