AOP(Aspect Oriented Programming)—面向切面编程
AOP 是一种思想, 是对某一类事情的集中处理
举个栗子
一个程序包含众多的方法, 每个方法都需要检测用户的登录状态
于是在每个方法中 ctrl + c / v 检测用户登录状态的代码
但这样做过于麻烦, 于是将检测用户登录状态的代码封装为一个方法, 这个操作可以被理解为是" AOP "
AOP 与 Spring AOP 之间的关系类似于 IOC 与 DI, MVC 与 Spring MVC
AOP—思想 / Spring AOP—思想的具体实现(框架)
AOP 的功能
AOP 的组成
定义
切面由切点和通知组成, 既包含了横切逻辑的定义, 也包括了连接点的定义
翻译
将切面理解为程序中用于处理某方面问题的一个类
该类包含很多的方法, 这些方法就是切点和通知
定义
切点的作用是提供一组规则来匹配连接点, 给满足规则的连接点添加通知
翻译
切点的作用是配置主动拦截的规则
定义
切面的工作被称为通知
翻译
触发 AOP 后具体的执行流程
通知的类型
@Before
注解@After
注解@AfterReturning
注解,@AfterThrowing
注解@Around
注解定义
应用执行过程中能够插入切面的一个点
这个点可以是方法调用时, 抛出异常时, 修改字段时…
切面代码可以利用这些点插入到应用的正常流程之中, 并添加新行为
翻译
可能触发 AOP 规则的所有点(所有请求)
举个栗子
切面 → 检测登录状态(处理某方面问题的一个类)
切点 → 设置检测登录状态的规则(配置主动拦截规则)
通知 → 触发设置的规则时执行的操作(触发 AOP 后具体的流程)
连接点 → 哪些情况下会涉及检测登录状态(可能触发 AOP 的所有请求)
在 Spring Boot 项目基础上引入下列依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
使用 Spring AOP 的步骤
定义切面
@Aspect // 表示当前类是切面
@Component
public class UserAspect {
}
定义切点
注意
切点无返回值
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
, 切点表达式
表示拦截 com → example → demo → controller → UserController 类下的所有方法(参数任意)
此处省略了修饰符
/**
* 切点(配置拦截规则)
* @author bibubibu
* @date 2023/7/5
*/
@Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
public void pointCut() {
}
定义通知
通知分为 5 种, 此处列举的为前置通知, 后置通知, 环绕通知
注意
/**
* 前置通知(需要声明针对的拦截规则 -> 切点)
* @author bibubibu
* @date 2023/7/5
*/
@Before("pointCut()")
public void beforeAdvice() {
System.out.println("执行了前置通知");
}
/**
* 后置通知(需要声明针对的拦截规则 -> 切点)
* @author bibubibu
* @date 2023/7/5
*/
@After("pointCut()")
public void afterAdvice() {
System.out.println("执行了后置通知");
}
/**
* 环绕通知(需要声明针对的拦截规则 -> 切点)
* @author bibubibu
* @date 2023/7/5
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("进入环绕通知");
Object obj = null;
// 执行目标方法
obj = joinPoint.proceed();
System.out.println("退出环绕通知");
return obj;
}
定义 UserController 类与 ArticleController 类
UserController 类
@RestController
@RequestMapping("/user")
public class UserController {
@RequestMapping("/process-u")
public String processU() {
System.out.println("执行了 processU()");
return "hello, UserController";
}
}
ArticleController 类
@RestController
@RequestMapping("/article")
public class ArticleController {
@RequestMapping("/process-a")
public String processA() {
System.out.println("执行了 processA()");
return "hello, ArticleController";
}
}
执行前置通知 + 后置通知
执行环绕通知 + 前置通知 + 后置通知
此处使用 Aspect J 语法
Aspect J 支持三种通配符
*
, 匹配任意字符, 只匹配一个元素(包 / 类 / 方法 / 方法参数)..
, 匹配任意字符, 可以匹配多个元素, 在表示类时, 必须和*
联合使用+
, 表示按照类型匹配指定类的所有类, 必须跟在类名后面(例如 com.cad.Car+), 表示继承该类的所有子类包括本身切点表达式由切点函数组成, 其中execution()
是最常用的切点函数, 用来匹配方法
execution()
语法
注意<>
与<>
之间的空格
execution(<修饰符> <返回类型> <包.类.方法名(参数)> <异常>)
修饰符(一般省略)
省略修饰符默认是*
修饰符 | 含义 |
---|---|
public | 公共的 |
private | 私有的 |
… | … |
* |
任意的 |
返回值类型(不能省略)
返回值类型 | 含义 |
---|---|
void | 无返回值类型 |
string | 返回字符串类型 |
… | … |
* |
任意类型 |
包
包 | 含义 |
---|---|
com.example.demo | 固定包 |
com.example.demo.* .service |
demo 包下的任意子包(以 service 结尾) |
com.example.demo.. |
demo 包下的所有子包(包含 demo 包) |
com.example.demo.* .service.. |
demo 包下的任意子包, 固定目录 service, service 目录下的所有子包(包含 service 包) |
类
类 | 含义 |
---|---|
UserController | 指定类 |
User* |
以 User 开头的所有类 |
* troller |
以 troller 结尾的所有类 |
* |
任意类 |
方法名(不能省略)
方法名 | 含义 |
---|---|
processu | 固定方法 |
pro* |
以 pro 开头所有方法 |
* ssu |
以 ssu 结尾的所有方法 |
* |
任意方法 |
(参数)
(参数) | 含义 |
---|---|
( ) | 无参 |
(int) | 一个整型 |
(int, int) | 两个整形 |
(…) | 任意参数 |
异常(可省略)
异常 → throws
省略表示不将异常作为匹配的条件(即忽略异常)
通常情况下不写异常
Spring AOP 是构建在动态代理的基础上, 因此 Spring 对 AOP 的支持局限于方法级别的拦截
未使用 Spring AOP
Spring AOP 主要基于 2 种方式实现
CGLIB 是 Java 中的动态代理框架, 主要作用是根据目标类和方法, 动态生成代理类
Java 中的动态代理框架, 几乎都是依赖字节码框架(例如 ASM, Javassist…)实现的
字节码框架是直接操作 class 字节码的框架. 可以加载已有的 class 字节码文件信息, 修改部分信息, 或动态生成 class
JDK 和 CGLIB 的区别
InvocationHandler
及Proxy
, 在运行时动态的在内存中生成了代理类对象, 该代理对象是通过实现同样的接口实现(类似静态代理接口实现的方式), 只是该代理类是在运行期时, 动态的织入统一的业务逻辑字节码来完成