一、AOP 简介
1. 什么是 AOP
AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构。
1. AOP 的作用
AOP是在不改原有代码的前提下对其进行增强。
3. AOP 核心概念
(1)连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等。在SpringAOP中,理解为方法的执行。
(2)切入点(Pointcut):匹配连接点的式子。在 SpringAOP 中,一个切入点可以只描述一个具体方法,也可以匹配多个方法。
1️⃣一个具体方法:【com.itheima.dao】包下的 BookDao 接口中的无形参无返回值的 save 方法
2️⃣匹配多个方法:所有的 save 方法,所有的 get 开头的方法,所有以 Dao 结尾的接口中的任意方法,所有带有一个参数的方法
(3)通知(Advice):在切入点处执行的操作,也就是共性功能。在 SpringAOP 中,功能最终以方法的形式呈现
(4)通知类:定义通知的类
(5)切面(Aspect):描述通知与切入点的对应关系
二、AOP入门案例
1. 思路分析
(1)导入坐标(pom.xml)
(2)制作连接点方法(原始操作,Dao接口与实现类)
(3)制作共性功能(通知类与通知)
(4)定义切入点
(5)绑定切入点与通知关系
2. 实现步骤
(1)添加依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
(2)定义接口与实现类
(3)定义通知类和通知
通知就是将共性功能抽取出来后形成的方法,共性功能指的就是当前系统时间的打印。
(4)定义切入点
说明:切入点定义依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
(5)制作切面
切面是用来描述通知和切入点之间的关系。绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置
@Component @Aspect public class MyAdvice { @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt() { } @Before("pt()") public void method() { System.out.println(System.currentTimeMillis()); } }
三、AOP 工作流程
1. AOP 工作流程
(1)Spring 容器启动
(2)读取所有切面配置中的切入点
(3)初始化bean,判定 bean 对应的类中的方法是否匹配到任意切入点
1️⃣匹配失败,创建对象
2️⃣匹配成功,创建原始对象(目标对象)的代理对象(4)获取bean执行方法
1️⃣获取bean,调用方法并执行,完成操作
2️⃣获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
2. AOP核心概念
(1)目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
(2)代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
四、AOP 配置管理
1. AOP切入点表达式
切入点: 要进行增强的方法
切入点表达式: 要进行增强的方法的描述方式
1.1 语法格式
对于切入点的描述:
(1)描述方式一:执行【 com.itheima.dao】 包下的 BookDao 接口中的无参数 update 方法
execution(void com.itheima.dao.BookDao.update())
(2)描述方式二:执行【com.itheima.dao.impl】包下的 BookDaoImpl 类中的无参数 update 方法
execution(void com.itheima.dao.impl.BookDaoImpl.update())
切入点表达式 标准格式 :动作关键字 ( 访问修饰符 返回值 包名 . 类 / 接口名 . 方法名 ( 参数 ) 异常
名)
1.2 通配符
使用通配符描述切入点,主要的目的就是简化之前的配置
1.3 书写技巧
2. AOP通知类型
AOP 通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置。
共提供了 5 种通知类型:
(1)前置通知
(2)后置通知
(3) 环绕通知 ( 重点 )
(4)返回后通知 ( 了解 )
(5)抛出异常后通知 ( 了解 )
通知类:
@Component @Aspect public class MyAdvice { @Pointcut("execution(void com.itheima.dao.BookDao.update())") private void pt() { } @Before("pt()") public void before() { System.out.println("before advice ..."); } @After("pt()") public void after() { System.out.println("after advice ..."); } @Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable { //获取执行签名信息 Signature signature = pjp.getSignature(); //通过签名获取执行类型(接口名) String className = signature.getDeclaringTypeName(); String methodName = signature.getName(); System.out.println("around before advice ..."); //通过签名获取执行操作名称(方法名) Object ret = pjp.proceed(); System.out.println("around after advice ..."); return ret; } @AfterReturning("pt()") public void afterReturning() { System.out.println("afterReturning advice ..."); } @AfterThrowing("pt()") public void afterThrowing() { System.out.println("afterThrowing advice ..."); } }
环绕通知注意事项
(1) 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知
(2)通知中如果未使用 ProceedingJoinPoint 对原始方法进行调用将跳过原始方法的执行
(3)对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为 Object 类型
(5) 原始方法的返回值如果是 void 类型,通知方法的返回值类型可以设置成 void, 也可以设置成 Object
(5) 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常
3. AOP通知获取数据
(1)获取切入点方法的参数,所有的通知类型都可以获取参数
1️⃣JoinPoint :适用于前置、后置、返回后、抛出异常后通知
2️⃣ProceedingJoinPoint :适用于环绕通知
(2)获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
1️⃣返回后通知
2️⃣环绕通知
(3)获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
1️⃣抛出异常后通知
2️⃣环绕通知
编写通知类:
@Component@Aspect
public class MyAdvice {
@Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
private void pt() {
}
@Before("pt()")
public void before(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("before advice ...");
}
@After("pt()")
public void after(JoinPoint jp) {
Object[] args = jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("after advice ...");
}
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("around before advice ...");
//通过签名获取执行操作名称(方法名)
Object ret = pjp.proceed(args);
System.out.println("around after advice ...");
return ret;
}
@AfterReturning(value = "pt()", returning = "ret")
public void afterReturning(Object ret) {
//ret是返回值
System.out.println("afterReturning advice ..." + ret);
}
@AfterThrowing(value = "pt()", throwing = "t")
public void afterThrowing(Throwable t) {
System.out.println("afterThrowing advice ..." + t);
}
}