文章目录
1. 概念
- AOP:Aspect Oriented Proramming 面向切面
- 作用:
- 在程序运行的时候,不修改源代码对程序进行增强
- 减少重复代码
- 提高开发效率
- 方便维护
- 实现原理:动态代理
- JoinPoint 连接点,指那些被拦截到的点,这些点指的是方法,因为 spring 只支持方法类型的连接点。
- Pointcut 切入点:是指我们要对哪些 Joinpoint 进行拦截的定义。
我们需要告诉增强的逻辑,它应该遇到什么方法才被执行,因此就需要切入点也提供说明。 - Advice 通知:增加的逻辑,通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Aspect 切面: 是切入点和通知的结合。
2. 使用
1. 先说结论
- 三要素:增强的逻辑,需要被增强的方法,两者之间的关联(Pointcut 切入点)
2. 快速入门(使用springboot)
- 先导入相关jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
-
提供一个:需要被增强的方法:
@Component public class Demo { /* 需求:在打印hello world之前输出一句日志 */ public void sayHello(){ System.out.println("Hello world"); } }
-
提供一个:增强的逻辑
@Component public class DemoAspect { public void LogRecord(){ System.out.println("我是要在输出hello world之前,打印日志"); } }
-
将 增强的逻辑 与 待被增强的方法 关联起来
@Component @Aspect // 定义切面:切面就是切入点和通知的结合 public class DemoAspect { // 前置通知:让增强的逻辑在待被增强的方法执行之前 执行 // execution 则是 切入点表达式 // 下面注解意思:在执行符合execution后面的方法之前,都先执行LogRecord()方法 @Before("execution(void com.jin.king.AopTest.Demo.sayHello())") public void LogRecord(){ // 这个就是增强的逻辑,也称为通知 System.out.println("我是要在输出hello world之前,打印日志"); } }
-
测试代码
@SpringBootTest class KingApplicationTests { @Autowired Demo demo; @Test void contextLoads() { demo.sayHello(); } }
-
结果图:
3. 切入点表达式书写
- 我们需要告诉增强的逻辑,它应该遇到什么方法才被执行,因此就需要切入点也提供说明,也就是切入点表达式表明。
- 如何确定一个方法:
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表) - 但是我们希望增强的逻辑,可以具有普遍性,可以给很多方法增强,因此切入点表达式就需要 写成 通用性。
- 符号*则代表任意。
根据上述快速入门的例子: 访问修饰符 返回值 包名.包名.包名....类名.方法名(参数列表) public void com.jin.king.AopTest.Demo.sayHello() 而访问修饰符可以省略。即: void com.jin.king.AopTest.Demo.sayHello() 代表:返回值是void 的 在这包com.jin.king.AopTest下的,此类Demo的方法sayHello() * com.jin.king.AopTest.Demo.sayHello() 代表:任意的返回值 在这包com.jin.king.AopTest下的,此类Demo的方法sayHello() * *.*.*.*.Demo.sayHello() 代表:任意的返回值 在这包任意包.下的,此类Demo的方法sayHello() 包名可以使用..表示当前包以及其子包,即 * *..*.Demo.sayHello() 代表:任意的返回值 在这包任意包下的,此类Demo的方法sayHello() 同理:类名,方法 * *..*.*.*() 代表:任意的返回值 在这包任意包下的,任意类下的任意方法 参数列表: 基本类型直接写名称 int 引用类型写包名.类名:java.lang.String 使用.. 代表任意 * *..*.*.*(..) 代表:任意的返回值 在这包任意包下的,任意类下的任意方法
4. 切入点注解@Pointcut
-
如果要重复写 切入点表达式 实在麻烦,因此出现 写一次,重复使用的方式
-
代码演示:
@Component @Aspect // 定义切面:切面就是切入点和通知的结合 public class DemoAspect { @Pointcut("execution(void com.jin.king.AopTest.*.*(..))") public void p1(){} // 一定要加括号 @Before("p1()") public void LogRecord(){ // 这个就是增强的逻辑,也称为通知 System.out.println("我是要在输出hello world之前,打印日志"); } @Before("p1()") public void LogRecord2(){ // 这个就是增强的逻辑,也称为通知 System.out.println("我是要在输出hello world之前,打印日志"); } }
5. 通知类型
1. 前置通知、后置通知、异常通知、最终通知
-
前置通知:在被增强方法调用之前执行。
-
后置通知:在被增强方法调用之后执行,(与异常通知无法同时存在)
-
异常通知:在被增强方法中出现异常的时候执行,(与后置通知无法同时存在)
-
最终通知:顾名思义,无论怎么样,都是最后被执行(类型finally块代码)
直接看代码例子:@Component @Aspect // 定义切面:切面就是切入点和通知的结合 public class DemoAspect { @Pointcut("execution(* com.jin.king.AopTest.Demo.*(..))") private void p1() { } @Before("p1()") private void beforeAspect(){ System.out.println("我是前置通知"); } @AfterReturning("p1()") private void afterReturningAspect(){ System.out.println("我是后置通知"); } @AfterThrowing("p1()") private void afterThrowing(){ System.out.println("我是异常通知"); } @After("p1()") private void afterAspect(){ System.out.println("我是最终通知"); } }
现在制造异常,如下:@Component public class Demo { /* 需求:对下面sayHello方法进行增强 */ public void sayHello(){ int i = 1/0; System.out.println("Hello world"); } }
执行测试代码:
2. 环绕通知
-
它是 spring 框架为我们提供的一种可以在代码中手动控制待被增强代码什么时候执行的方式。重点:手动控制。
-
解析:在增强的逻辑中,我突然想执行 被增强的方法,就触发使其执行。
-
spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
-
在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
-
通常情况下,环绕通知都是独立使用的
-
初步使用环绕通知:
@Component @Aspect // 定义切面:切面就是切入点和通知的结合 public class DemoAspect { @Pointcut("execution(* com.jin.king.AopTest.Demo.*(..))") private void p1() { } @Around("p1()") private void afterAspect(){ System.out.println("我是环绕通知"); } }
看截图我们发现:卧槽,怎么只执行了增强的方法,原来的方法没有被触发呢,是的,因为没有手动去触发。修改代码:
@Component @Aspect // 定义切面:切面就是切入点和通知的结合 public class DemoAspect { @Pointcut("execution(* com.jin.king.AopTest.Demo.*(..))") private void p1() { } @Around("p1()") private void afterAspect(ProceedingJoinPoint proceedingJoinPoint){ System.out.println("我是环绕通知"); // 获取原来方法所需要的参数 Object[] args = proceedingJoinPoint.getArgs(); try { // 类 前置通知 System.out.println("执行原来方法执行"); // 手动触发 原来的方法 proceedingJoinPoint.proceed(args); // 类 后置通知 System.out.println("执行原来方法之后"); } catch (Throwable t) { // 类 异常通知 System.out.println("原来方法执行异常的时候"); t.printStackTrace(); } finally { // 类 最终通知 System.out.println("最终执行的方法"); } } }
- 我们发现其实 环绕通知 也可以是其他通知的结合,只不过逻辑都是自己写而已。
- ProceedingJoinPoint ,可以作为入参,可以执行原来的即待增强的方法。
3. AOP实战(调用接口信息入库的小工具)
- 请看这篇博客:https://blog.csdn.net/xueyijin/article/details/122011673