目录
一. AOP 思想 和 Spring AOP
二. AOP 的组成
三. Spring AOP 的实现
1. 框架支持
2. 定义切面和切点
拦截表达式的说明
3. 定义相关通知
4. 创建连接点
四. Spring AOP 的实现原理
AOP 是面向切面编程,是一种思想,可以理解为对某一类事情的集中处理,例如登陆权限的校验。使用 AOP 之前,在每一处需要判断用户登录的场景的时候,都需要各自去实现调用来完成对用户登录的验证。有了 AOP 之后,就只需要在某一处进行配置,就可以对需要判断用户登录的场景进行统一处理。
AOP 是一种思想,而 Spring AOP 框架就是对 AOP 思想的一种实现,这样的关系就类似于 IOC 和 DI。
1. 切面(类):理解为某一方面的具体内容,比如用户的登录判断就可以理解为是一个 "切面" ;
2. 切点(方法):理解为一个拦截规则,也就是哪一块内容需要去处理登录判断,哪一块不需要去处理用户判断;
3. 通知(方法具体实现代码):理解为执行具体的 AOP 业务逻辑;
3.1 前置通知:在目标方法 (实际要执行的方法) 调用之前执行的通知;
3.2 后置通知:在目标方法调用之后执行的通知;
3.3 环绕通知:在目标方法调用前,后都会执行;(比前置通知快,比后置通知慢)
3.4 异常通知:在目标方法抛出异常的时候执行的通知;
3.5 返回通知:在目标方法返回的时候执行通知;
4. 连接点: 理解为可能触发切点的点,是根据切点中的拦截规来的;
Spring AOP 的框架支持需要在 maven 库中添加,在spring boot中找不到框架支持;
org.springframework.boot
spring-boot-starter-aop
2.7.10
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 类下的所有方法,不需要关注方法的参数;
定义通知就是被拦截的方法具体要实现的业务逻辑。通常使用到的注解:
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;
}
}
@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 是一种框架,针对 AOP 面向切面的一种实现。构建在动态代理的基础上的,基于动态代理实现的;
通过动态代理来实现,就可以理解为程序为了实现某一种功能做了某一层筛选,而这一层筛选,就是通过代理来实现的,这时候,也就不需要每种具体功能实现都由目标对象来实现了。
在没有实现代理类的时候,是调用者直接去调用目标对象,这意味着目标对象此时需要做更多的事情,例如目标对象是添加文章,在添加文章之前需要做登录校验,如果有了动态代理,代理对象此时可以为调用者进行登录校验,此时目标对象可以不用直接和调用者接触,动态代理可以为我们作出信息筛选,只有条件满足了,才会将到目标对象去执行,不满足就直接返回了,此时对于目标对象,可以专注与做添加文章的操作,而不需要为别的事情考虑,别的事情交给代理对象来完成即可。
Spring AOP 支持 JDK Proxy 和 CGLIB 的方式实现动态代理,JDK是基于接口实现的,CGLIB是基于类的子类实现的;
JDK 动态代理底层是通过反射来实现的,CGLIB底层是通过字节码增强技术生成子类实现的:例如现在有一个A类,通过一些手段来构造一个A的子类,子类又包含父类的所有属性方法,此时就可以通过子类的方法来调用父类,在大家看来这个子类是不存在的,这个子类就是通过CGLIB内库是构建的一个虚拟类,构建虚拟类的这个技术就是字节码增强技术;