明月几时有,把酒问青天。——唐代李白《将进酒》
在 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 是一个基于 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.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.7version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.9.7version>
dependency>
其中,aspectjweaver
依赖是必须的,它包含了 AspectJ 的运行时环境;aspectjrt
依赖是可选的,它包含了 AspectJ 的运行时库,如果使用了 AspectJ 的注解,建议添加该依赖。
在添加了依赖后,可以在启动类上添加 @EnableAspectJAutoProxy
注解开启 AspectJ 支持,或者在切面类上使用 @Aspect
注解定义切面类。
Spring AOP 只支持方法级别的切面
Spring AOP 只支持方法级别的切面指的是,Spring AOP 只能拦截方法的执行,而不能拦截其他级别的切面,例如字段级别、构造方法级别、类级别等。
这是因为 Spring AOP 的实现方式是基于 JDK 动态代理或 CGLIB 字节码生成技术的,而 JDK 动态代理和 CGLIB 都是基于方法级别的。在使用 JDK 动态代理时,代理对象必须实现一个或多个接口,而接口是方法集合的抽象,因此只能对接口中定义的方法进行拦截;在使用 CGLIB 字节码生成技术时,会生成一个新的类作为代理类,该类是原始类的子类,因此只能拦截原始类中的方法,无法拦截字段、构造方法等。
不过,Spring AOP 还提供了其他的一些拦截点,例如:
Bean 生命周期事件:可以在 Bean 初始化前后、销毁前后等事件中执行通知。
AspectJ 注解:可以使用 @AspectJ 注解来实现更为灵活的切面拦截,支持字段级别、构造方法级别、类级别等切入点。
需要注意的是,使用 AspectJ 注解时,需要添加相应的 AspectJ 依赖,并配置 AspectJ 编译器,以支持注解的解析和织入。
通过 @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一样, 也会通过责任链来执行所有的方法增强。