1. Spring Boot AOP 是什么?
1.1 AOP 是什么?
AOP
是面向切面编程(Aspect-Oriented Programming)的缩写。AOP
是一种编程范式,旨在经过将横切关注点(cross-cutting concerns)从首要业务逻辑中分离出来,供给一种更好的代码模块化和可维护性,换句话说,便是对某一类事情的集中处理。
横切关注点指的是在应用程序中横跨多个模块或层的功用,例如日志记载、事务管理、安全性、缓存、反常处理等。
例如:在不运用AOP
的情况下,每个Controller
都要写一遍用户登录验证。当功用越来越多的时分,需求在每个功用里都写相同的代码,这就提高了代码的修正和维护的本钱。对于这种功用一致,且运用的地方较多的功用,就能够考虑AOP
来一致处理了。
1.2 Spring Boot AOP 是什么?
Spring Boot AOP
是依据Spring
结构和Spring AOP
的AOP
完成办法,专门针对Spring Boot
应用程序供给的一种简化装备和运用的办法。
Spring AOP
是Spring
结构供给的一种AOP
完成办法。AOP
是一种编程范式,而Spring AOP
是Spring
结构对AOP
的详细完成。
2. AOP 的组成
AOP的组成有:切面、连接点、切点、告诉
2.1 切面(Aspect)
切面是横跨一个或多个类的模块化单元,它界说了与横切关注点相关的行为。切面由切点、告诉组成,它通常以类的方法表明。
2.2 切点(Pointcut)
切点(Pointcut)在面向切面编程(AOP)中起到了选择性阻拦和应用切面的效果,它能够被理解为一种规矩。
比方:有一个用户对别人的文章进行点评,这时分需求检测该用户是否登录,只要登录后才干点评。这便是切点,它相当于一种规矩。
2.3 告诉(Advice)
告诉是切面的一部分,它是在特定切点处履行的详细操作。切面由切点和告诉组成,切点用于界说在哪些连接点上应用告诉的规矩,而告诉界说了在这些连接点上履行的详细操作。在办法上增加相应的注解就表明相应的告诉:
- 前置告诉(@Before):在方针办法履行之前履行的告诉。能够在该告诉中进行一些准备工作或参数验证。
- 后置告诉(@After):在方针办法履行之后履行的告诉。能够在该告诉中进行一些清理工作或记载日志。
- 回来告诉(@AfterReturning):在方针办法成功履行并回来成果后履行的告诉。能够在该告诉中对办法的回来值进行处理或履行其他操作。
- 反常告诉(@AfterThrowing):在方针办法抛出反常后履行的告诉。能够在该告诉中处理反常或履行相应的反常处理逻辑。
- 盘绕告诉(@Around):在方针办法履行之前和之后都履行的告诉。它能够完全操控方针办法的履行进程,包含是否履行方针办法以及怎么处理回来值和反常。
2.4 连接点(Join Point)
连接点是指在应用程序履行进程中的特定点或事情,例如办法的调用、办法的履行、反常的抛出、属性的拜访等。**它是AOP中能够刺进切面逻辑的地方。**详细来说,连接点是在程序履行期间能够被阻拦的点。当程序运转到某个连接点时,AOP
结构能够介入并履行相应的切面逻辑。
3. Spring Boot AOP 的演示
3.1 增加 Spring Boot AOP 依靠
增加如下的代码在 pom.xml
文件中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.11</version>
</dependency>
3.2 界说切面与切点
切面一般是一个类,它里面有切点与告诉,下面是切点的界说办法:
@Aspect //切面
@Component //不能省掉,要在项目发动的时分发动
public class UserAOP {
//切点(装备阻拦规矩)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}
}
为什么是空办法?由于这个办法的效果是当一个标识
。
@Pointcut
注解里的表达式便是规矩,它的含义:
-
@Pointcut
注解用于界说切点,即被切入的方位。 -
"execution(* com.example.demo.controller.UserController.*(..))"
是切点表达式的内容。 -
execution()
是切点指示符,表明匹配办法的履行。 -
*
表明匹配恣意回来类型的办法。 -
com.example.demo.controller.UserController
是方针类的全限定名,表明匹配该类。 -
*
表明匹配类中的恣意办法名。 -
(..)
表明匹配恣意参数的办法。
综合起来,这个切点表达式的意思是匹配 com.example.demo.controller.UserController
类中的一切办法,不管办法的回来类型和参数怎么。换言之,便是UserController
中的一切办法都被阻拦了。
execution
里的语法规矩:
execution(<修饰符><回来类型><包.类.办法(参数)><反常>)
其中,修饰符、反常部分能够省掉,其它的不能省掉。
-
*
:匹配恣意字符,能够匹配零个或多个字符。在切点表达式中,*
通配符能够用于匹配包、类或办法的称号中的恣意字符部分。 -
..
:匹配恣意字符,能够匹配零个或多个字符、类或包路径。在切点表达式中,..
通配符能够用于匹配类或包路径的恣意部分,例如com.example..
表明匹配com.example
包及其子包下的一切内容。 -
+
:表明按照类型匹配指定类的一切子类。在切点表达式中,+
通配符用于表明指定类的一切子类,包含该类自身。例如,com.example.demo.controller.UserController+
表明匹配UserController
类及其一切子类。
3.3 界说告诉
办法在被阻拦后需求做处理,处理便是告诉。
(1)前置告诉 + 后置告诉
@Aspect //切面
@Component //不能省掉,要在项目发动的时分发动
public class UserAOP {
//切点(装备阻拦规矩)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}
//前置告诉
@Before("pointcut()")
public void doBefore(){
System.out.println("履行前置告诉" + LocalDateTime.now());
}
//后置告诉
@After("pointcut()")
public void doAfter(){
System.out.println("履行后置告诉" + LocalDateTime.now());
}
}
@Before
、@After
等注解中的属性表明需求匹配的连接点(这里便是),以确认在哪些方位要应用切面的告诉。
下面是Controller
的代码:
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/hi")
public String hi(){
System.out.println("履行 UserController 的 hi() 办法");
return "do user";
}
@RequestMapping("/login")
public String login(){
System.out.println("履行 UserController 的 login() 办法");
return "do login";
}
}
运转并拜访:
(2)盘绕告诉
@Aspect //切面
@Component //不能省掉,要在项目发动的时分发动
public class UserAOP {
//切点(装备阻拦规矩)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}
//盘绕告诉
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("开端履行盘绕告诉:");
Object obj = joinPoint.proceed();
System.out.println("结束盘绕告诉");
//这里的 obj 便是 连接点办法的回来值,能够对其进行修正
obj = "do Around " + obj;
System.out.println(obj);
return obj;
}
}
在办法doAround
中,参数ProceedingJoinPoint joinPoint
表明连接点(即方针办法),它能够在盘绕告诉中被调用和操作。
代码的履行次序如下:
- 当连接点被触发时,即方针办法即将履行前,盘绕告诉
doAround
会被履行。 - 榜首行代码输出”开端履行盘绕告诉:”,表明盘绕告诉开端履行。
-
joinPoint.proceed()
调用表明持续履行方针办法,此行代码会触发方针办法的履行,并将方针办法的回来值存储在obj
变量中。 - 第三行代码输出”结束盘绕告诉”,表明盘绕告诉的履行已经结束。
- 下一行的代码对方针办法的回来值进行修正,将其改为”do Around ” + obj,并将修正后的值赋给
obj
变量。 - 接着,输出修正后的
obj
的值。 - 最终,将修正后的
obj
回来作为方针办法的成果。
成果:
(3)前置、后置告诉 + 盘绕告诉
@Aspect //切面
@Component //不能省掉,要在项目发动的时分发动
public class UserAOP {
//切点(装备阻拦规矩)
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut(){}
//前置告诉
@Before("pointcut()")
public void doBefore(){
System.out.println("履行前置告诉" + LocalDateTime.now());
}
//后置告诉
@After("pointcut()")
public void doAfter(){
System.out.println("履行后置告诉" + LocalDateTime.now());
}
//盘绕告诉
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("开端履行盘绕告诉:");
Object obj = joinPoint.proceed();
System.out.println("结束盘绕告诉");
//这里的 obj 便是 连接点办法的回来值,能够对其进行修正
obj = "do Around " + obj;
System.out.println(obj);
return obj;
}
}
成果:
(4)回来告诉
//回来告诉
@AfterReturning("pointcut()")
public void AfterReturning(){
System.out.println("履行回来告诉");
}
成果:
(5)反常告诉
//反常告诉
@AfterThrowing("pointcut()")
public void AfterThrowing(){
System.out.println("履行反常告诉");
// 能够在此处进行反常处理逻辑
}
@RequestMapping("/login")
public String login(){
System.out.println("履行 UserController 的 login() 办法");
throw new ArrayIndexOutOfBoundsException();
}
成果:
4. Spring AOP 完成原理
4.1 动态署理
Spring AOP
是构建在动态署理基础上,因而 Spring
对 AOP
的⽀持局限于⽅法级别的阻拦。
Spring AOP的扼要完成原理:
- 切入点表达式解析:Spring AOP首要解析界说的切入点表达式,该表达式指定了哪些办法将被织入切面逻辑。
- 切面逻辑界说:开发人员界说切面逻辑,包含要在方针办法履行前、履行后或抛出反常时履行的逻辑。
- 创立署理方针:Spring运用动态署理机制,在运转时动态地创立一个署理方针,该署理方针完成了方针方针所完成的接口。署理方针将会阻拦方针方针的办法调用。
- 办法阻拦:当客户端调用方针方针的办法时,实践上是调用署理方针的对应办法。署理方针会阻拦这个办法调用并履行切面逻辑。
- 履行切面逻辑:在署理方针的对应办法中,依据切入点表达式判断是否需求履行切面逻辑。假如需求,署理方针会调用切面逻辑的相关办法。
- 调用方针办法:在切面逻辑履行结束后,署理方针会持续调用方针方针的实践办法。
织入(Weaving)是指将切面逻辑与方针类进行结合的进程。署理方针的生成机遇与织入机遇有关。依据织入机遇的不同,能够将织入分为以下三种办法:
- 编译期织入(Compile-Time Weaving):
- 切面在方针类编译时被织入。
- 需求特别的编译器来支撑,例如AspectJ的织入编译器。
- 在方针类编译期间,切面逻辑被直接织入方针类的字节码中。
- 类加载期织入(Load-Time Weaving):
- 切面在方针类加载到JVM时被织入。
- 需求特别的类加载器(ClassLoader)来支撑。
- 在方针类被引进应用之前,经过增强方针类的字节码来织入切面逻辑。
- AspectJ5的加载时织入(Load-Time Weaving, LTW)支撑这种办法。
- 运转期织入(Runtime Weaving):
- 切面在应用运转的某一时刻被织入。
- 一般情况下,AOP容器会为方针方针动态创立署理方针。
- 署理方针经过阻拦方针方针的办法调用,在适当的机遇履行切面逻辑。
- Spring AOP便是以运转期织入的办法来织入切面。
Spring AOP
支撑两种类型的署理:JDK动态署理
和CGLIB署理
。假如方针方针完成了InvocationHandler
接口,Spring
将运用JDK动态署理来创立署理方针。假如方针方针没有完成InvocationHandler
接⼝,Spring
将运用CGLIB
署理,经过继承方针方针来创立署理方针。
它们首要区别:
- 接口要求:JDK动态署理要求方针方针完成接口,而CGLIB署理能够署理没有完成接口的类。
- 生成办法:JDK动态署理运用Java的反射机制生成署理方针,而CGLIB署理运用CGLIB库生成署理方针,经过修正方针类的字节码来完成。
- 署理方针类型:JDK动态署理生成的署理方针是完成了方针方针所完成的接口,而CGLIB署理生成的署理方针是方针方针的子类。