AOP(Aspect Oriented Programming):面向切编程。是对某⼀类事情的集中处理,例如网站的登录验证,不使用 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 实现
一开始 spring 官方没有实现 AOP,因此有个非官方的组织使用 AspectJ 来实现 AOP,后来官方也实现了 Spring AOP,但是发现很多人使用 AspectJ 的语法习惯了,因此 Spring AOP 就兼容了 AspectJ。
添加依赖
pom.xml:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency>
定义切面、切点、通知:
package com.example.springaopdemo.common;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect // 创建切面
@Component // 托管到 spring 容器,随着项目启动而启动
public class UserAOP {// 创建切点// 只会拦截符合表达式规则的,下一步是否要停止它的执行要看需求,跟过海关一样@Pointcut("execution(* com.example.springaopdemo.controller.UserController.* (..))")public void doPoint() {}// 创建通知// 前置通知@Before("doPoint()")public void before() {System.out.println("执行前置通知方法");}// 后置通知@After("doPoint()")public void after() {System.out.println("执行后置通知方法");}// return前通知@AfterReturning("doPoint()")public void afterReturning() {System.out.println("执行return前通知方法");}// 抛出异常前通知@AfterThrowing("doPoint()")public void afterThrowing() {System.out.println("执行抛出异常前通知方法");}// 环绕通知@Around("doPoint()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("开始执行环绕通知方法");Object obj = joinPoint.proceed();System.out.println("结束执行环绕通知方法");return obj;}}
关于切点表达式说明:
语法为:execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
代码执行(连接点):
Spring AOP 实现原理
使用动态代理的方式实现的。Spring AOP 使用两种方式实现动态代理:
1. JDK Proxy:要求被代理的类一定要实现接口。(底层通过反射实现,速度快)
2. CGLIB:因为是通过代理类的子类来实现动态代理的,所以要求代理类不能被 final 修饰。(底层通过字节码增强技术实现)