目录
一. AOP 思想 和 Spring AOP
AOP 是面向切面编程,是一种思想,可以理解为对某一类事情的集中处理,例如登陆权限的校验。使用 AOP 之前,在每一处需要判断用户登录的场景的时候,都需要各自去实现调用来完成对用户登录的验证。有了 AOP 之后,就只需要在某一处进行配置,就可以对需要判断用户登录的场景进行统一处理。
AOP 是一种思想,而 Spring AOP 框架就是对 AOP 思想的一种实现,这样的关系就类似于 IOC 和 DI。
二. AOP 的组成
1. 切面(类):理解为某一方面的具体内容,比如用户的登录判断就可以理解为是一个 "切面" ;
2. 切点(方法):理解为一个拦截规则,也就是哪一块内容需要去处理登录判断,哪一块不需要去处理用户判断;
3. 通知(方法具体实现代码):理解为执行具体的 AOP 业务逻辑;
3.1 前置通知:在目标方法 (实际要执行的方法) 调用之前执行的通知;
3.2 后置通知:在目标方法调用之后执行的通知;
3.3 环绕通知:在目标方法调用前,后都会执行;(比前置通知快,比后置通知慢)
3.4 异常通知:在目标方法抛出异常的时候执行的通知;
3.5 返回通知:在目标方法返回的时候执行通知;
4. 连接点: 理解为可能触发切点的点,是根据切点中的拦截规来的;
三. Spring AOP 的实现
1. 框架支持
Spring AOP 的框架支持需要在 maven 库中添加,在spring boot中找不到框架支持;
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.10</version>
</dependency>
2. 定义切面和切点
1. 使用 @Aspect 和五大类注解定义切面;
2. 使用@Pointcut("execution()") 注解定义切点,在 execution() 中实现拦截规则,并且定义一个空方法,不需要由方法体,主要是起到一个"标识"作用,标识下面的通知方法具体是指哪个切点。
@Aspect
@Component // 组件
public class UserAOP {
@Pointcut("execution(public * com.example.demo.controller.UserController.*(..))")
// 定义空方法,记住方法名
public void pointcut(){
}
拦截表达式的说明
execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
修饰符和异常通常可以省略;
主要涉及到三种通配符:
1. * 表示匹配任意字符,只匹配一个元素,元素可以是包,类,方法等;
2. .. 表示匹配任意字符,可以匹配多个元素,在表示类的时候,必须与 * 联合使用;
3. + 表示继承该类的所有子类包括其本身,必须跟在类名后面;
例如上述代码就表示匹配:public 修饰符下,com.example.demo.controller 包下的 UserController 类下的所有方法,不需要关注方法的参数;
3. 定义相关通知
定义通知就是被拦截的方法具体要实现的业务逻辑。通常使用到的注解:
1. 前置通知:@Befor
2. 后置通知:@After
3. 环绕通知:@Around
4. 异常通知:@AfterThrowing
5. 返回通知: @AfterReturning
在每一个注解中,都需要说明是对于哪个切点的,对应的内容就是切点定义的方法名;
// 加两个注解变为切面
@Aspect
@Component // 组件
public class UserAOP {
public void pointcut(){
}
// 前置通知
@Before("pointcut()")
public void doBefore(){
System.out.println("执行了前置通知:"+ LocalDateTime.now());
}
// 后置通知
@After("pointcut()")
public void doAfter(){
System.out.println("执行了后置通知:"+ LocalDateTime.now());
}
// 环绕通知 ProceedingJoinPoint 表示事件(方法)本身
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("开始执行环绕通知"); // 比前置快执行
Object obj = proceedingJoinPoint.proceed();
System.out.println("结束执行环绕通知"); // 比后置慢执行
return obj;
}
}
4. 创建连接点
@RestController
public class UserController {
@RequestMapping("/user")
public Object sayHi(){
System.out.println("执行了 sayHi 方法");
return "hello";
}
@RequestMapping("user2")
public Object sayHi2(){
System.out.println("执行了 sayHi2 方法");
return "hello2";
}
}
四. Spring AOP 的实现原理
Spring AOP 是一种框架,针对 AOP 面向切面的一种实现。构建在动态代理的基础上的,基于动态代理实现的;
通过动态代理来实现,就可以理解为程序为了实现某一种功能做了某一层筛选,而这一层筛选,就是通过代理来实现的,这时候,也就不需要每种具体功能实现都由目标对象来实现了。
在没有实现代理类的时候,是调用者直接去调用目标对象,这意味着目标对象此时需要做更多的事情,例如目标对象是添加文章,在添加文章之前需要做登录校验,如果有了动态代理,代理对象此时可以为调用者进行登录校验,此时目标对象可以不用直接和调用者接触,动态代理可以为我们作出信息筛选,只有条件满足了,才会将到目标对象去执行,不满足就直接返回了,此时对于目标对象,可以专注与做添加文章的操作,而不需要为别的事情考虑,别的事情交给代理对象来完成即可。
Spring AOP 支持 JDK Proxy 和 CGLIB 的方式实现动态代理,JDK是基于接口实现的,CGLIB是基于类的子类实现的;
JDK 动态代理底层是通过反射来实现的,CGLIB底层是通过字节码增强技术生成子类实现的:例如现在有一个A类,通过一些手段来构造一个A的子类,子类又包含父类的所有属性方法,此时就可以通过子类的方法来调用父类,在大家看来这个子类是不存在的,这个子类就是通过CGLIB内库是构建的一个虚拟类,构建虚拟类的这个技术就是字节码增强技术;