aop🚓
- AOP 分类
- AspectJ | 高级但是难用
- Spring AOP | 易用但仅支持方法
- aop 原理
明月几时有,把酒问青天。——唐代李白《将进酒》
AOP 分类
在 Spring Boot 中,AOP 的实现主要有以下几种:
-
基于 AspectJ 的 AOP:这是一种基于 Java 语言扩展(Java Language Extension,JLE)的 AOP 实现方式,支持静态织入和动态织入两种方式。
-
Spring AOP:这是 Spring 框架自带的 AOP 实现方式,基于动态代理机制实现,只支持方法级别的切面。
-
自定义注解实现 AOP:通过自定义注解和 AOP 技术实现业务逻辑和切面逻辑的分离,便于代码的维护和扩展。
其中,基于 AspectJ 的 AOP 功能最为强大,但使用难度较大;Spring AOP 简单易用,但功能有一定限制;自定义注解实现 AOP 灵活性较高,但需要自行实现 AOP 切面逻辑。在实际开发中,应根据项目需要选择合适的 AOP 实现方式。
Spring AOP 是基于代理的 AOP,而 AspectJ 是基于字节码的 AOP
Spring AOP 和 AspectJ 都是 Spring Framework 中的 AOP 实现方式。Spring AOP 是基于代理的 AOP,而 AspectJ 是基于字节码的 AOP。
在 Spring Boot 中,默认情况下使用的是 Spring AOP。如果需要使用 ,可以通过在启动类上添加 @EnableAspectJAutoProxy(proxyTargetClass = true)
注解来开启。
如果需要使用 AspectJ, 需要引入 AspectJ jar包
@EnableAspectJAutoProxy 不是用来开启 AspectJ 的吗
配置 @EnableAspectJAutoProxy, 不引入 AspectJ jar包的情况下就使用 Spring AOP
@EnableAspectJAutoProxy
注解可以用来开启 AspectJ 的支持,但是它也是用来开启 Spring AOP 的。
在 Spring 中,Spring AOP 和 AspectJ 都是实现 AOP 的方式之一。@EnableAspectJAutoProxy
注解用来开启 Spring AOP 的支持,如果在注解中设置 proxyTargetClass=true
,则会使用 CGLIB 来创建代理对象,否则会使用 JDK 的动态代理。
当然,在 Spring Boot 中,如果需要使用 AspectJ,也可以在启动类上添加 @EnableAspectJAutoProxy(proxyTargetClass = true)
注解来开启 AspectJ 支持
AspectJ | 高级但是难用
AspectJ中的静态织入和动态织入
AspectJ 是一个基于 Java 语言的切面编程框架,它提供了两种切面织入方式:静态织入和动态织入。
静态织入是指在编译期间将切面代码织入到目标类中,生成一个新的字节码文件。在运行期间,JVM 加载这个字节码文件,并且这个字节码文件包含了目标类和切面代码,从而实现对目标类的增强。静态织入的优点是织入后的代码执行效率高,缺点是需要在编译期间进行织入,增加了额外的工作量和复杂度。
动态织入是指在运行期间将切面代码织入到目标类中,不需要在编译期间进行修改。在运行期间,通过 JVM 提供的动态代理技术或字节码生成技术,将切面代码织入到目标类中,从而实现对目标类的增强。动态织入的优点是织入更加灵活,可以在运行期间进行修改,缺点是织入后的代码执行效率相对较低。
在 AspectJ 中,静态织入和动态织入可以通过不同的方式实现。静态织入可以使用 AspectJ 编译器,将切面代码编译成字节码文件,并将其与目标类一起打包成一个 jar 包或 war 包,从而在运行期间加载这个包,实现对目标类的增强。动态织入可以使用 AspectJ 提供的 Runtime 织入功能,或者使用 Spring AOP 框架提供的动态代理技术,实现对目标类的增强。
AspectJ 支持更加灵活的切面编程,可以拦截不仅仅是方法级别的切面,还可以拦截字段、构造方法、类等级别的切面。
- 字段级别
要拦截字段级别的切面,可以使用 AspectJ 提供的 field 切点匹配器,例如:
javaCopy code
@Aspect
public class FieldAspect {
@Before("get(* com.example.demo.model.User.name)")
public void beforeFieldAccess(JoinPoint joinPoint) {
// 拦截 User 类中的 name 字段的 getter 方法
}
}
在上面的示例中,使用 get 切点匹配器匹配 User 类中的 name 字段的 getter 方法,实现对字段级别的拦截。
- 构造方法级别
要拦截构造方法级别的切面,可以使用 AspectJ 提供的 initialization 切点匹配器,例如:
@Aspect
public class ConstructorAspect {
@Before("execution(com.example.demo.model.User.new())")
public void beforeConstructorExecution(JoinPoint joinPoint) {
// 拦截 User 类的构造方法
}
}
在上面的示例中,使用 execution 切点匹配器匹配 User 类的构造方法,实现对构造方法级别的拦截。
- 类级别
要拦截类级别的切面,可以使用 AspectJ 提供的 type 切点匹配器,例如:
@Aspect
public class ClassAspect {
@Before("execution(* com.example.demo.service.*.*(..)) && type(service)")
public void beforeServiceMethodExecution(JoinPoint joinPoint) {
// 拦截 com.example.demo.service 包下的所有方法,并且方法所属的类必须被 @Service 注解标注
}
}
在上面的示例中,使用 execution 切点匹配器匹配 com.example.demo.service 包下的所有方法,并且方法所属的类必须被 @Service 注解标注,实现对类级别的拦截。
需要注意的是,AspectJ 的切面匹配器比 Spring AOP 更加灵活,可以匹配更多的切点。但是,AspectJ 的使用需要在编译期间进行织入,因此相对来说也更加复杂和耗费资源。在实际使用中,需要根据实际需求选择使用 Spring AOP 还是 AspectJ。
使用 AspectJ, 要引入什么jar包
Spring Boot 默认使用 Spring AOP,如果要使用 AspectJ,需要手动引入 AspectJ 相关的依赖。
可以在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
其中,aspectjweaver
依赖是必须的,它包含了 AspectJ 的运行时环境;aspectjrt
依赖是可选的,它包含了 AspectJ 的运行时库,如果使用了 AspectJ 的注解,建议添加该依赖。
在添加了依赖后,可以在启动类上添加 @EnableAspectJAutoProxy
注解开启 AspectJ 支持,或者在切面类上使用 @Aspect
注解定义切面类。
Spring AOP | 易用但仅支持方法
Spring AOP 只支持方法级别的切面
Spring AOP 只支持方法级别的切面指的是,Spring AOP 只能拦截方法的执行,而不能拦截其他级别的切面,例如字段级别、构造方法级别、类级别等。
这是因为 Spring AOP 的实现方式是基于 JDK 动态代理或 CGLIB 字节码生成技术的,而 JDK 动态代理和 CGLIB 都是基于方法级别的。在使用 JDK 动态代理时,代理对象必须实现一个或多个接口,而接口是方法集合的抽象,因此只能对接口中定义的方法进行拦截;在使用 CGLIB 字节码生成技术时,会生成一个新的类作为代理类,该类是原始类的子类,因此只能拦截原始类中的方法,无法拦截字段、构造方法等。
不过,Spring AOP 还提供了其他的一些拦截点,例如:
-
Bean 生命周期事件:可以在 Bean 初始化前后、销毁前后等事件中执行通知。
-
AspectJ 注解:可以使用 @AspectJ 注解来实现更为灵活的切面拦截,支持字段级别、构造方法级别、类级别等切入点。
需要注意的是,使用 AspectJ 注解时,需要添加相应的 AspectJ 依赖,并配置 AspectJ 编译器,以支持注解的解析和织入。
aop 原理
通过 @EnableAspectJAutoProxy 将aop的功能通过bean后置处理器, 关联到我们的IOC容器中
EnableAspectJAutoProxy 的目的是为了导入 AnnotationAwareAspectJAutoProxyCreator
AnnotationAwareAspectJAutoProxyCreator 又是bean后置处理器, 这不就挂钩上了吗
史上最完整的AOP底层原理_哔哩哔哩_bilibili
一文吃透spring aop底层原理_spring aop的底层原理_吴法刚的博客-CSDN博客
那么AOP具体是如何在我体内运行的呢?在我启动时会创建IOC容器, 同时将我体内的bean进行三个连续的动作,
-
构造
-
填充
-
属性初始化
AOP功能就是通过我体内一个专门处理AOP的bean后置处理器 DefaultAdvisorAutoProxyCreator 进行方法增强的,
可以算作IOC容器的附加功能, 所有的后置处理器都在bean构造完, 并且填充了属性之后执行
-
填充了属性之后执行
-
在每一个bean初始化之后都会调用这个后置处理器的postProcessorAfterInitialization 方法, 在这个方法里为需要使用AOP的bean创建代理对象。
-
先通过getAdvicesAndAdvisorsForBean方法, 获取所有的增强Advice, 同时判断当前bean是否满足我们配置的切面条件
如果满足条件的话, 就会为这个bean构造代理对象来实现AOP
为了更统一更方便的构造代理对象, 我会先搭建一个专门用来构造生产代理对象的工厂, proxyFactory, 我会告诉这个工厂具体选择哪种方式进行代理, 分别是cglib和jdkProxy。
通过添加@EnableAspectJAutoProxy注解, 并且将其中proxyTargetClass配置改为 true 强制使用cglib。
当然啦在spring boot中默认就是使用cglib, 不过只有这个配置为false, 同时该类实现了任意接口才会使用jdkProxy, 否则还是会使用cglib方式。
ProxyFactory知道使用哪种方式之后, 就会构造jdkDynamicAopProxy或者cglibAopProxy, 然后就可以通过他们的getProxy方法获得真正的代理对象。
jdkDynamicAopProxy
先说相对简单, 而且即将被我无情放弃的jdkDynamicAopProxy, 在getProxy中会构造一个实现同样bean接口的代理对象, 将真实bean作为代理, 对象中的一个成员变量。
在调用bean方法的时候就会执行代理对象中的 invoke 方法
这个 invoke 方法只有两步,
-
我会通过之前提到的execution表达式, 获取所有与该方法匹配的所有增强方法, 并将它们组成调用链同时进行排序
-
开始按顺序执行这些调用链, 这里的调用方式就是经典的责任链模式, 在调用中间会插入bean 执行bean真实的方法
cglibAopProxy
最为常用的cglibAopProxy, 同样会在getProxy方法中构造代理对象
用增强器 Enhancer 来设置代理基本信息以及增强方法的调用链
接着执行 Enhancer create方法来生成代理对象
和jdkDynamicAopProxy不同的是cglib是基于jdk rt jar包中的asm来生成一组新的class文件, 然后实例化它的对象, 所以对于没有实现接口的bean也可以生成代理对象
在调用bean方法的时候, 会先执行代理对象的intercept方法, 与jdkProxy一样, 也会通过责任链来执行所有的方法增强。