目录
[1. 什么是Spring AOP](#1. 什么是Spring AOP)
[2. AOP的组成](#2. AOP的组成)
[2.1 切面(Aspect)](#2.1 切面(Aspect))
[2.2 连接点(Join Point)](#2.2 连接点(Join Point))
[2.3 切点(Pointcut)](#2.3 切点(Pointcut))
[2.4 通知(Advice)](#2.4 通知(Advice))
[3. Spring AOP的实现](#3. Spring AOP的实现)
[3.1 添加Spring AOP框架支持](#3.1 添加Spring AOP框架支持)
[3.2 定义切面和切点以及通知](#3.2 定义切面和切点以及通知)
[4. Spring AOP的实现原理](#4. Spring AOP的实现原理)
1. 什么是Spring AOP
AOP(Aspect Oriented Programming):面向切面回答,它是⼀种思想,它是对某⼀类事情的集中处理。比如用户登录权限的效验,没学 AOP 之前,我们所有需要判断用户登录的页面(中的方法),都要各自实现或调用用户验证的方法,然而有了 AOP 之后,我们只需要在某⼀处配置⼀下,所有需要判断用户登录页面(中的方法)就全部可以实现用户登录验证了,不再需要每个方法中都写相同的用户登录验证了。
AOP是一种思想,而Spring AOP是一个框架,提供了对AOP思想的实现.就相当于IOC和DI类似.
AOP除了统一的登录判断之外,AOP还就可以实现:
2. AOP的组成
1. 切面
2. 切点
3. 连接点
3. 通知
2.1 切面(Aspect)
切面由切点和通知组成,即包含了横切逻辑的定义,页包括了连接点的定义.
切面是包含:通知,切点和切面的类,相当于AOP实现的某个功能的集合.
通俗的说,在程序中就是处理某个具体问题的一个类,里面包含了许多方法,这些方法就是切点和通知.
2.2 连接点(Join Point)
应用执行过程中能够插⼊切面的⼀个点,这个点可以是方法调用时,抛出异常时,甚至修改字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
通俗的来说: 有可能触发拦截规则(AOP规则)的所有点(就是所有的请求)
2.3 切点(Pointcut)
切点就是一种规则,用来匹配连接点,当连接点符合切点的规则的时候就会给这个连接点进行添加通知.
通俗的来说:切点的作用:进行主动拦截的规则,承上启下,匹配连接点,添加通知
2.4 通知(Advice)
切面也是有目标的 ------它必须完成的工作。在 AOP 术语中,切面的工作被称之为通知。
通俗的来说:主动进行拦截,拦截成功执行的方案就是通知. === AOP具体的处理动作
通知的执行,可以分为五种.
-
- 前置通知: @Before
-
- 后置通知: @After
-
- 返回之后通知: @AfterReturning
-
- 抛异常后通知: @AfterThrowing
-
- 环绕通知: @Around
3. Spring AOP的实现
接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的目标是拦截所有 UserController 里面的方法,每次调用UserController 中任意⼀个方法时,都执行相应的通知事件。Spring AOP 的实现步骤如下:
3.1 添加Spring AOP框架支持
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.2 定义切面和切点以及通知
我们创建一个类,给这个类进行添加@Aspect(表示这是一个切面)@Component(表示这个类随着Spring的启动而启动)
- 创建切面
- 构造切点
以上我们定义了拦截的规则,表示我们将拦截com.example.demo.controller.UserController这个类中的所有方法
切断点的表达式说明:
AspectJ 支持三种通配符
* :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
.. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使用。
- :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有子类包括本身
execution(* com.cad.demo.User.*(..)) :匹配 User 类里的所有方法。
execution(* com.cad.demo.User+.*(..)) :匹配该类的子类包括该类的所有⽅法。
execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有方法。
execution(* com.cad..*.*(..)) :匹配 com.cad 包下、子孙包下所有类的所有方法。
execution(* addUser(String, int)) :匹配 addUser 方法法,且第⼀个参数类型是 String,第⼆个参数类型是 int。
- 定义通知
以上是定义了两个简单的通知,我们平常使用的是环绕通知
完整代码:
java
package com.example.demo.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Created with IntelliJ IDEA.
* Description:
* User: YAO
* Date: 2023-07-13
* Time: 19:18
*/
// 1. 定义切面
@Aspect // 表示当前类是一个切面
@Component // 当前类随着Spring的启动而启动
// 两个注解缺一不可
public class UserAspect {
// 2.定义切点
// 使用注解@Pointcut 加 AspectJ 表达式语法 定义拦截规则
@Pointcut("execution(* com.example.demo.controller.UserController.* (..))")
//拦截这个目录下的所有方法 com.example.demo.controller.UserController.
public void pointCut(){
}
// 4.定义通知
// 通知一定要对应相应的岚切规则,一个拦截规则可以执行多个通知
// 4.1 执行前通知
// @before()参数:针对拦截规则的方法名
@Before("pointCut()")
public void doBefore(){
System.out.println("执行前置通知");
}
// 4.2 执行后通知
@After("pointCut()")
public void doAfter(){
System.out.println("执行后置通知");
}
// 4.3 环绕通知
@Around("pointCut()")
// 环绕通知必须要有返回值,交给框架进行下一步操作
public Object doAround(ProceedingJoinPoint joinPoint){
Object object = null;
System.out.println("Around ⽅法开始执⾏");
try {
object = joinPoint.proceed();
} catch (Throwable e ) {
e.printStackTrace();
}
System.out.println("Around ⽅法结束执⾏");
return object;
}
}
浏览器进行发送请求
控制台进行打印
4. Spring AOP的实现原理
Spring AOP 是构建在动态代理的基础上的,因此Spring对AOP的支持局限于方法级别的拦截.
那么什么是动态代理呢?
动态代理:就不进行直接访问,通过代理的方式进行访问
举例: 访问国外网站,国内无法直接进行访问,我们可以通过添加代理的方式进行访问,使用国外的服务器进行转发请求进行访问.
AOP就是构建在动态代理的基础之上的,就是通过使用切点设置拦截规则,使得用户不能直接访问,而是必须进行访问切点这个代理才能,进行验证之后进行访问.
动态代理在Spring AOP中有两种:
1.JDK Proxy (默认情况下实现接口的类使用)
2.CGLIB (没有实现接口的类会基于这种方式生成代理类)
织入: 代理的生成时机.
Spring AOP生成代理的时机是在运行,就是切面在应用运行的某一时刻被织入,一般请开你改下,在织入切面的时候,AOP容器回味目标对象动态的创建一个代理对象.
JDK 和 CGLIB 实现的区别
JDK 实现,要求被代理类必须实现接口,之后是通过 InvocationHandler 及 Proxy,在运行时动态的在内存中生成了代理类对象,该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式),只是该代理类是在运行期时,动态的织入统一的业务逻辑字节码来完成。
CGLIB 实现,被代理类可以不实现接口,是通过继承被代理类,在运行时动态的生成代理类对象。
Spring中默认使用jdk动态代理 SpringBoot进行了改进默认使用CGLIB