Spring AOP
- 1. AOP概述
- 2. Spring AOP快速⼊⻔
- 2.1 引⼊AOP依赖
- 2.2 编写AOP程序
- 3. Spring AOP详解
- 3.1 SpringAOP核⼼概念
- 3.1.1 切点(Pointcut)
- 3.1.2 连接点(JoinPoint)
- 3.1.3 通知(Advice)
- 3.1.4 切⾯(Aspect)
- 3.2 通知类型
- 3.3 @PointCut
- 3.4 切⾯优先级@Order
- 3.5 切点表达式
- 3.5.1 execution表达式
- 3.5.2 @annotation
- 3.5.2.1 ⾃定义注解@MyAspect
- 3.5.2.2 切面类
- 3.5.2.3 添加⾃定义注解
- 4. Spring AOP原理
- 4.1 代理模式
- 4.1.1 静态代理
- 4.1.2 动态代理
1. AOP概述
AOP是Spring框架的第⼆⼤核⼼(第⼀⼤核⼼是IoC)
- 什么是 AOP?
- AspectOrientedProgramming(⾯向切⾯编程)
- 什么是⾯向切⾯编程呢?切⾯就是指某⼀类特定问题,所以AOP也可以理解为⾯向特定⽅法编程.
- 简单来说: AOP是⼀种思想,是对某⼀类事情的集中处理
- 什么是SpringAOP?
- AOP是⼀种思想,它的实现⽅法有很多,有SpringAOP,也有AspectJ、CGLIB等.
AOP就可以做到在不改动这些原始⽅法的基础上,针对特定的⽅法进⾏功能的增强.
AOP的作⽤:在程序运⾏期间在不修改源代码的基础上对已有⽅法进⾏增强(⽆侵⼊性:解耦)
2. Spring AOP快速⼊⻔
学习什么是AOP后,我们先通过下⾯的程序体验下AOP的开发,并掌握Spring中AOP的开发步骤.
需求:统计图书系统各个接⼝⽅法的执⾏时间.
2.1 引⼊AOP依赖
在pom.xml⽂件中添加配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2.2 编写AOP程序
记录Controller中每个⽅法的执⾏时间
运⾏程序,观察⽇志
对程序进⾏简单的讲解:
- @Aspect:标识这是⼀个切⾯类
- @Around: 环绕通知,在⽬标⽅法的前后都会被执⾏.后⾯的表达式表⽰对哪些⽅法进⾏增强.
- ProceedingJoinPoint.proceed() 让原始⽅法执⾏
整个代码划分为三部分
我们通过AOP⼊⻔程序完成了业务接⼝执⾏耗时的统计.
通过上⾯的程序,我们也可以感受到AOP⾯向切⾯编程的⼀些优势:
- 代码⽆侵⼊:不修改原始的业务⽅法,就可以对原始的业务⽅法进⾏了功能的增强或者是功能的改
- 减少了重复代码
- 提⾼开发效率
- 维护⽅便
3. Spring AOP详解
下⾯我们再来详细学习AOP,主要是以下⼏部分
- SpringAOP中涉及的核⼼概念
- SpringAOP通知类型
- 多个AOP程序的执⾏顺序
3.1 SpringAOP核⼼概念
3.1.1 切点(Pointcut)
切点(Pointcut), 也称之为"切⼊点"
Pointcut 的作⽤就是提供⼀组规则(使⽤AspectJpointcutexpressionlanguage来描述),告诉程序对哪些⽅法来进⾏功能增强.
3.1.2 连接点(JoinPoint)
满⾜切点表达式规则的⽅法,就是连接点.也就是可以被AOP控制的⽅法
以⼊⻔程序举例,所有
com.example.demo.controller
路径下的⽅法,都是连接点.
上述BookController中的⽅法都是连接点
切点和连接点的关系
连接点是满⾜切点表达式的元素.切点可以看做是保存了众多连接点的⼀个集合.
3.1.3 通知(Advice)
通知就是具体要做的⼯作,指哪些重复的逻辑,也就是共性功能(最终体现为⼀个⽅法)
⽐如上述程序中记录业务⽅法的耗时时间,就是通知.
在AOP⾯向切⾯编程当中,我们把这部分重复的代码逻辑抽取出来单独定义,这部分代码就是通知的内容.
3.1.4 切⾯(Aspect)
切⾯(Aspect)=切点(Pointcut)+通知(Advice)
通过切⾯就能够描述当前AOP程序需要针对于哪些⽅法,在什么时候执⾏什么样的操作.
切⾯既包含了通知逻辑的定义,也包括了连接点的定义.
切⾯所在的类,我们⼀般称为切⾯类(被@Aspect注解标识的类)
3.2 通知类型
上⾯我们讲了什么是通知, 接下来学习通知的类型.
@Around 就是其中⼀种通知类型,表⽰环绕通知. Spring中AOP的通知类型有以下⼏种:
- @Around:环绕通知,此注解标注的通知⽅法在⽬标⽅法前,后都被执⾏
- @Before:前置通知,此注解标注的通知⽅法在⽬标⽅法前被执⾏
- @After:后置通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,⽆论是否有异常都会执⾏
- @AfterReturning: 返回后通知,此注解标注的通知⽅法在⽬标⽅法后被执⾏,有异常不会执⾏
- @AfterThrowing:异常后通知,此注解标注的通知⽅法发⽣异常后执⾏
- 正常运⾏的情况
程序正常运⾏的情况下, @AfterThrowing
标识的通知⽅法不会执⾏从上图也可以看出来,
@Around 标识的通知⽅法包含两部分,⼀个"前置逻辑",⼀个"后置逻辑".其中"前置逻辑"会先于@Before 标识的通知⽅法执⾏,"后置逻辑"会晚于@After 标识的通知⽅法执⾏
- 异常时的情况
注意事项:
- @Around 环绕通知需要调⽤ ProceedingJoinPoint.proceed() 来让原始⽅法执⾏,其他通知不需要考虑⽬标⽅法执⾏.
- @Around 环绕通知⽅法的返回值,必须指定为Object,来接收原始⽅法的返回值,否则原始⽅法执⾏完毕,是获取不到返回值的.
- ⼀个切⾯类可以有多个切点.
3.3 @PointCut
上述代码就可以修改为:
当切点定义使⽤private修饰时,仅能在当前切⾯类中使⽤,当其他切⾯类也要使⽤当前切点定义时,就需要把private改为public.引⽤⽅式为:全限定类名.⽅法名()
3.4 切⾯优先级@Order
当我们在⼀个项⽬中,定义了多个切⾯类时,并且这些切⾯类的多个切⼊点都匹配到了同⼀个⽬标⽅法.当⽬标⽅法运⾏的时候,这些切⾯类中的通知⽅法都会执⾏,那么这⼏个通知⽅法的执⾏顺序是什么样的呢?
Spring 给我们提供了⼀个新的注解,来控制这些切⾯通知的执⾏顺序:@Order
3.5 切点表达式
上⾯的代码中,我们⼀直在使⽤切点表达式来描述切点.下⾯我们来介绍⼀下切点表达式的语法.
切点表达式常⻅有两种表达⽅式
- execution(…):根据⽅法的签名来匹配
- @annotation() :根据注解匹配
3.5.1 execution表达式
execution() 是最常⽤的切点表达式,⽤来匹配⽅法,语法为:
其中:访问修饰符和异常可以省略
3.5.2 @annotation
实现步骤:
- 编写⾃定义注解
- 使⽤@annotation 表达式来描述切点
- 在连接点的⽅法上添加⾃定义注解
3.5.2.1 ⾃定义注解@MyAspect
创建⼀个注解类(和创建Class⽂件⼀样的流程,选择Annotation就可以了)
3.5.2.2 切面类
使⽤@annotation 切点表达式定义切点,只对@MyAspect ⽣效
切⾯类代码如下:
3.5.2.3 添加⾃定义注解
Spring AOP 的实现方式
4. Spring AOP原理
上⾯我们主要学习了SpringAOP的应⽤,接下来我们来学习SpringAOP的原理,也就是Spring是如何实现AOP的.
Spring AOP是基于动态代理来实现AOP的,咱们学习内容主要分以下两部分
- 代理模式
- Spring AOP源码剖析
4.1 代理模式
代理模式,也叫委托模式.
定义:为其他对象提供⼀种代理以控制对这个对象的访问.它的作⽤就是通过提供⼀个代理类,让我们在调⽤⽬标⽅法的时候,不再是直接对⽬标⽅法进⾏调⽤,⽽是通过代理类间接调⽤.
在某些情况下,⼀个对象不适合或者不能直接引⽤另⼀个对象,⽽代理对象可以在客⼾端和⽬标对象之间起到中介的作⽤.
使⽤代理前:
使⽤代理后:
代理模式的主要⻆⾊
- Subject: 业务接⼝类.可以是抽象类或者接⼝(不⼀定有)
- RealSubject: 业务实现类. 具体的业务执⾏,也就是被代理对象.
- Proxy: 代理类.RealSubject的代理.
⽐如房屋租赁
Subject 就是提前定义了房东做的事情,交给中介代理,也是中介要做的事情
RealSubject: 房东
Proxy: 中介
UML类图如下:
代理模式可以在不修改被代理对象的基础上,通过扩展代理类,进⾏⼀些功能的附加与增强.
根据代理的创建时期,代理模式分为静态代理和动态代理.
- 静态代理:由程序员创建代理类或特定⼯具⾃动⽣成源代码再对其编译,在程序运⾏前代理类的.class ⽂件就已经存在了.
- 动态代理:在程序运⾏时,运⽤反射机制动态创建⽽成.
4.1.1 静态代理
静态代理:在程序运⾏前,代理类的.class⽂件就已经存在了.
上⾯这个代理实现⽅式就是静态代理(仿佛啥也没⼲).
从上述程序可以看出,虽然静态代理也完成了对⽬标对象的代理,但是由于代码都写死了,对⽬标对象的每个⽅法的增强都是⼿动完成的,⾮常不灵活. 所以⽇常开发⼏乎看不到静态代理的场景
4.1.2 动态代理
相⽐于静态代理来说,动态代理更加灵活.
我们不需要针对每个⽬标对象都单独创建⼀个代理对象,⽽是把这个创建代理对象的⼯作推迟到程序运⾏时由JVM来实现.也就是说动态代理在程序运⾏时,根据需要动态创建⽣成.
⽐如房屋中介,我不需要提前预测都有哪些业务,⽽是业务来了我再根据情况创建.
Java也对动态代理进⾏了实现,并给我们提供了⼀些API,常⻅的实现⽅式有两种:
- JDK动态代理
- CGLIB动态代理
动态代理在我们⽇常开发中使⽤的相对较少,但是在框架中⼏乎是必⽤的⼀⻔技术.学会了动态代理之后,对于我们理解和学习各种框架的原理也⾮常有帮助.
JDK动态代理
JDK动态代理类实现步骤
- 定义⼀个接⼝及其实现类(静态代理中的HouseSubject 和RealHouseSubject )
- ⾃定义InvocationHandler 并重写 invoke⽅法在invoke ⽅法中我们会调⽤⽬标⽅法(被代理类的⽅法)并⾃定义⼀些处理逻辑
- 通过Proxy.newProxyInstance(ClassLoader loader,Class<?>[]
interfaces,InvocationHandler h) ⽅法创建代理对象
CGLIB动态代理
Spring 默认使用的代理方法如下