目录
一、Spring AOP 是什么?
二、学习AOP 有什么作用?
三、AOP 的组成
3.1、切面(Aspect)
3.2、切点(Pointcut)
3.3、通知(Advice)
3.4、连接点
四、实现 Spring AOP 一个简单demo
4.1、添加 Spring AOP 框架依赖
4.2、定义切面
4.3、定义切点
4.3.1、切点表达式说明
4.4、定义通知
4.5、实现原理
编辑
五、Spring AOP 实现原理
5.1、原理概述
5.2、织入
5.3、动态代理
5.4、面试题:DK 和 CGLIB 实现的区别
一、Spring AOP 是什么?
AOP 就是面向切面的编程, 是一种思想,是对某一类事情的集中处理。
例如登陆权限的检验(检测当前用户是否登录),在学习 AOP 前,我们会将这样一个检测机制封装成一个方法,在需要检测的地方调用该方法即可,但想象以下,首先,如果这个方法有 1000 个地方要进行调用, 那么你去一个一个写调用函数很累,其次,一旦这个登陆检查方法需要修改,比如增加一个参数,那么,你就需要修改 1000 方法调用的参数... 但如果你会 AOP 后,我们只需要在某处配置一下,就可以实现用户的登录检测,不需要每一个方法中都写登录检验的调用方法啦!
AOP 是一种思想,而 Spring AOP 是一个框架,提供了对 AOP 思想的实现(类似于 IoC 和 DI 的关系)。
二、学习AOP 有什么作用?
例如刚刚我们所讲到的登录检测机制,这一个方法一旦需要调用的地方多了,不仅写起来麻烦,维护起来成本也是很高的,所以,对于这种功能统一,且使用地方较多的功能,就可以考虑 AOP 来进行统一的处理!
例如以下常见的使用场景:
- 统一登录检测机制。
- 统一方法的执行时间统计。
- 统一的返回格式设置。
- 统一异常处理。
- 事务的开始和提交。
Ps: AOP 是对某一功能进行的统一处理,大大降低了代码维护的成本,所以可以说 AOP 是 OOP 的补充和完善~
三、AOP 的组成
3.1、切面(Aspect)
切面,在程序中就是对某一功能进行统一处理的类, 这个类里包含了很多方法,这些方法就是由 切点 和 通知 组成。
3.2、切点(Pointcut)
用来进行主动拦截的规则(配置)。
这里拦截的是什么,过程是什么样的呢?就是对用户向服务器发送的请求进行拦截,检测用户的操作是否符合预期,发现问题并统一处理的过程,如下图:
3.3、通知(Advice)
通知就是 AOP 的具体执行动作。具体的,在程序中被拦截后会触发一个具体的动作,就是通知中具体实现的业务代码。
在 Spring 中,可以在方法上使用以注解,设置方法为通知方法,被拦截后满足条件就会调用通知方法:
- 前置通知(@Before):执行 目标方法(被拦截的方法)之前执行的方法。
- 后置通知(@After):执行了目标方法之后执行的方法。
- 返回之后通知(@AfterReturning):目标方法执行了返回数据(return)时,执行的方法。
- 抛异常后通知(@AfterThrowing):在执行目标方法出现异常时,执行的方法。
- 环绕通知(@Around):在目标方法执行的周期范围内(执行之前,执行中,执行后)都可以执行的方法(Ps:如果已经有了前置和后置通知,再使用环绕通知,那么周期范围就在前置通知之前 ~ 后置通知之后)。
3.4、连接点
会触发 AOP 规则的所有的点(所以请求)。
四、实现 Spring AOP 一个简单demo
4.1、添加 Spring AOP 框架依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
Ps:
1.创建 Spring Boot 项目时是没有 Spring AOP 框架可以选择的。
2.添加 Spring AOP 框架可以去中央仓库,值得注意的是要选择 Spring Boot 对应的 AOP ,而不是 Spring 对应的 AOP。
3.最好选择 Spring Boot 对应版本的 AOP ,以上就是 2.7.9版本。
4.2、定义切面
使用 @Aspect 注解修饰类,告诉框架是一个切面类。
import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;
@Aspect //告诉框架我是一个切面类
@Component
public class UserAspect {
}
4.3、定义切点
使用 @Pointcut 修饰一个方法,它不需要由方法体。方法名就是起到一个标识的作用,标识通知方法具体指的是哪一个切点(切点可能有多个)。
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //告诉框架我是一个切面类
@Component
public class UserAspect {
/**
* 切点:配置拦截规则
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut() {
}
}
4.3.1、切点表达式说明
AspectJ ⽀持三种通配符,如下:
- * :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
- .. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
- + :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的 所有⼦类包括本身
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法如下(注意使用空格进行分割):
execution(<修饰符> <返回类型> <包.类.⽅法(参数)> <异常>)
其中,修饰符和异常可以省略,具体含义如下:
4.4、定义通知
使用通知方法中的五个注解,其中前置通知、后置通知、环绕通知最常用,那么以下代码我将用这三个注解来举例:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
@Aspect //告诉框架我是一个切面类
@Component
public class UserAspect {
/**
* 切点:配置拦截规则
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointcut() {
}
/**
* 前置通知
*/
@Before("pointcut()")
public void beforeAdvice() {
System.out.println("执行了前置通知");
}
/**
* 后置通知
*/
@After("pointcut()")
public void aftereAdvice() {
System.out.println("执行了后置通知");
}
/**
* 环绕通知
* @param joinPoint
* @return
* @throws Throwable
*/
@Around("pointcut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进入了环绕通知~");
Object obj = null;
obj = joinPoint.proceed();
System.out.println("退出了环绕通知~");
return obj;
}
}
执行结果如下:
4.5、实现原理
五、Spring AOP 实现原理
5.1、原理概述
Spring 的切面是代理类实现的,包裹了目标对象,也就是说,用户只能先通过代理类,进行校验,如果没有问题才会进一步访问到目标对象。
5.2、织入
织入简单理解就是代理生成的时机,一般情况下,在 Spring AOP 动态代理的植入时机是程序的运行期。
5.3、动态代理
Spring AOP 是建立在动态代理的基础上,因此 Spring 对 AOP 的支持局限于方法级别的拦截。
Spring AOP 支持 JDK Proxy 和 CGLIB 方式实现动态代理。默认情况下,实现了接口的类,使用 AOP 会基于 JDK 生成代理类,没有实现接口的类,会基于 CGLIB 生成代理类。
JDK 和 CGLIB 底层都是基于反射实现的。、
5.4、面试题:DK 和 CGLIB 实现的区别
1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏ 时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。
2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类对象。