目录
[AOP 概述](#AOP 概述)
[Spring AOP 快速使用](#Spring AOP 快速使用)
[编写 AOP 程序](#编写 AOP 程序)
[Spring AOP 详解](#Spring AOP 详解)
[连接点(Join Point)](#连接点(Join Point))
[切面优先级 @Order](#切面优先级 @Order)
[execution 表达式](#execution 表达式)
[Spring AOP 原理](#Spring AOP 原理)
[JDK 动态代理](#JDK 动态代理)
[CGLIB 动态代理](#CGLIB 动态代理)
[Spring AOP 源码](#Spring AOP 源码)
AOP 概述
AOP:Aspect Oriented Programming(面向切面编程)
提到面向切面编程,易想到之前的一点知识:面向对象编程,Java 是一门面向对象的编程。面向切面 与 面向对象并不矛盾,面向切面是对面向对象的补充。
这里的切面指,某一类特定的问题,所以 AOP 也称面向特定方法编程。
面向特定方法编程:例如前面我们举出的案例,强制登录校验拦截器,就是为了解决登录校验这一问题而做出的统一处理;统一异常处理,是为了解决抛出异常而做出的统一处理;统一数据处理,是为了解决数据返回格式做出的统一处理
AOP 是一种思想,拦截器,是 AOP 的一种实现。Spring 实现了这种思想,提供了拦截器的相关接口。
简单来讲:AOP 是一种思想,对某一类问题,某一类事件做集中处理。
AOP 的实现方法有很多,Spring AOP,ASpectJ,CGLIB 等,Spirng AOP 只是实现 AOP 的一种方式而已。
前面的拦截器,统一数据处理,统一异常处理,只是 AOP 思想的一些实现应用。AOP 作用的维度可以更加细致(可以根据包,类,方法名,参数等等进行拦截),能够实现更加复杂的逻辑。
例:我们现在有一个项目,其中有很多业务。我们要统计以下所有业务的执行耗时。
可以在业务方法执行前后,记录开始时间和结束时间,两者之差就是方法耗时。

但我们的项目存在很多业务模块,业务模块中又存在不同接口,不同接口又存在不同方法,在每个方法都进行如上操作,代码极为冗余,且工作量较大。
==》
AOP 可以在不改动上述方法的基础上,对已有的方法做出处理增强!(无侵入,解耦合)。
Spring AOP 快速使用
引入依赖
在 pom.xml 文件添加如下配置:

编写 AOP 程序
记录 controller 中每个方法的执行时间

程序简单分析:
@Aspect:表示这是一个切面类,用来处理某一个特定问题。
@Around:表示这个切面类侵入程序的时机,环绕目标函数前后执行
Around 括号中的内容表示该程序对那些方法程序生效
@ProceedingJoinPoint pjp,Object proceed = pjp.proceed() 表示执行目标方法
整个代码可以分为三部分:

优点:

Spring AOP 详解
核心概念
切点(Pointcut)
作用:提供一组规则,告诉程序要对那些方法生效进行功能增强

表达式 execution(* com.example.demo.controller.*.*(..)) 就是切点表达式
连接点(Join Point)
满足切点表达式规则的方法,就是连接点。即可以被 AOP 控制的方法
以上面程序为例,连接点就是 com.example.demo.controller 路径下的所有方法
也就是 ProceedingJoinPoint

所有满足切点表达式的方法都是连接点,即切点,是满足要求的连接点的集合
通知(Advice)
指具体要做的事,重复的逻辑,也就是共性功能,我们抽象体现为一个方法
如上面的程序中,记录业务方法的耗时,就是通知

在 AOP 中,将部分重复的代码逻辑抽象出来单独定义,就是通知里的内容
切面(Aspect)
切面 = 切点 + 通知类
通过切面就能描述当前 AOP 程序需要针对那些方法,在什么时候执行什么操作

切面所在的类,我们一般称为 切面类(Aspect 注解标识)
总结:

通知类型


测试程序:

正常运行情况:

@Around 标识的通知方法包含两部分,一个前置逻辑,一个后置逻辑,前置逻辑会先于 @Before 标识的同时方法执行,后置逻辑会晚于 @After 标识的通知方法执行
异常:

@AfterReturning 标识的通知方法不会执行
@Around 的后置逻辑也不会执行,但 @After 标识的通知方法会执行
@Around 标识的通知方法,必须要调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑。
@Around 环绕通知方法的返回值,必须指定为 Object 来接收原始方法的返回值,且必须要有返回值,否则当原始方法执行完毕后,无法获取到返回值
一个切面类可以有多个切点
@PointCut
上面代码案例中,存在大量的重复切点表达式,Spring 提供了 @PointCut 注解,将公共的切点表达式提取出来。

定义切点时,用 private 修饰时候,则只能在当前类进行使用。当其他切面类也需要使用该切点定义时,将 private 改为 public。引用方式为:全限定类名.方法名()

切面优先级 @Order
当我们在项目中,定义了多个切面类,并且这些切面类的多个切入点都匹配到了同一个目标方法,则这几个通知方法的执行顺序如何?
存在多个切面类,默认按照切面类的字母顺序排序:
@Before 字母靠前的先执行
@After 字母靠后的先执行
Spring 给我们提供了另外一个注解,来控制切面执行的顺序 @Order

@Order 标识的切面类:
@Before:数字越小越先执行
@After:数字越大越先执行

切点表达式
切点表达式语法如下:
有两种表达方式
-
execution(.......):根据方法签名来匹配
-
@annotation(......) :根据注解来匹配
execution 表达式
语法如下:

访问修饰符和异常可以省略


@annotation
匹配多个无规则的方法,例如:不同包下的不同类的不同方法
@annotation 实现:
-
编写自定义注解
-
使用 @annotation 表达式来描述切点
-
在连接点的方法上添加自定义注解
- 编写自定义注解:创建 Java 类时候选择 annotation


@Target 注解标识了 Annotation 所修饰的对象范围,即该注解可以用在什么地方

@Retention 注解标识了 Annotation 的生命周期

- 使用 @annotation 表达式来描述切点
使用 @annotation 切点表达式来定义切点,只对 @MyAspect 生效

- 在连接点的方法上添加自定义注解

测试符合预期。
Spring AOP 的实现方式(面试题)
基于注解 @Aspect 实现
基于自定义注解 @annotation 实现
基于 Spring API (xml 配置 aop config)
基于代理实现 (久远的实现方式)
Spring AOP 原理
Spring AOP 是基于动态代理来实现 AOP 的
代理模式
定义:为其他对象提供一种代理以控制对这个对象的访问。主要作用是提供一个代理类,在调用目标方法的时候,不再直接调用目标方法,而是通过代理类简介使用。
在某些情况下,一个对象不适合或不能直接引用另一个对象,而代理对象就可以在客户端和目标对象之间起到中介代理的作用。

代理模式的角色:
-
Subject(提前定义了房东做的事,交给中介代理):业务接口类,可以是抽象类或者是接口(不一定有)
-
RealSubject(房东):业务实现类,具体的业务执行,也就是被代理对象
-
Proxy(中介):代理类,RealSubject 的代理
代理模式可以在不修改代理对象的基础上,通过扩展代理类,进行一些功能的增强。
根据代理的创建时期,代理模式可以分为静态代理和动态代理
静态代理:由程序员创建代理类或特定工具来自动生成源代码再进行编译,在程序运行前,代理类的 .class 文件就已经存在了
动态代理:在程序运行时,运用反射机制动态创建而成
静态代理
在程序运行之间,代理类的 .class 文件就已经存在了(在出租房子之前,中介已经做好了相关的工作,就等租户来租房子了)





上面的代理实现就是静态代理。
虽然静态代理完成了对目标对象的代理,但由于代码都写死了,对目标对象的每个方法的增强都是手动完成了,及其不灵活。
当我们增加新的需求,中介新增其他业务为:代理房屋出售


即,从上面栗子可以看出,增加新的需求后,修改接口和业务实现类的同时,还需要修改代理。
存在大量冗余的代码。
动态代理
在动态代理中,我们不需要针对每个目标对象都单独创建一个代理对象,而是把创建代理对象这项工作交给程序运行时由 JVM 来实现,也就是说动态代理在程序运行时,是根据需要动态创建生成的。
(比如房屋中介,并不需要提前预测都有那些业务,而是业务来了之后再根据情况创建)
Java 对动态代理进行了实现,并提供了一些 API,常见的实现方式有两种:
-
JDK 动态代理
-
CGLIB 动态代理
JDK 动态代理
JDK 动态代理实现步骤:
-
定义一个接口以及其实现类(即静态代理中的 HouseSubject 和 RealHouseSubject)
-
实现 InvocationHandler 接口并重写 invoke 方法,在 invoke 方法中,我们会调用目标方法,并做一些功能增强的逻辑
-
通过 Proxy.newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) 方法创建代理对象


代码讲解:


CGLIB 动态代理
JDK 动态代理只能代理实现了接口的类。
CGLIB 允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式来实现代理。
在 Spring AOP 模块中,如果目标对象实现了接口,则默认采用 JDK 动态代理,否则采用 CGLIB 动态代理。
CGLIB 动态代理实现步骤:
-
定义一个类(被代理类)
-
自定义方法 MethodInterceptor 并重写 intercept 方法,intercept 用于增强目标方法,和 JDK 代理中的 invoke 方法类似
-
通过 Enhancer 类的 create() 创建代理类



代码讲解:


Spring AOP 源码
Spring AOP 主要是基于两种方式:JDK 和 CGLIB
Spring 对于 AOP 的实现,基本是靠 AnnotationAwareAspectJAutoProxyCreator 完成
其生成代理对象的逻辑在父类的 AbstractAutoProxyCreator 中
AbstractAutoProxyCreator 中的 createProxy 中为创建代理的逻辑

在 buildProxy 中又封装使用了 buildProxy 方法

在 buildPorxy 方法中,实现了生成代理对象的逻辑
上面流程中,我们也可以发现,有一个属性值十分重要:proxyTargetClass。

Spring 中,默认该属性为 false,如果实现了接口,使用 JDK 代理,如果是普通类,则使用 CGLIB 代理。
Spring Boot 从 2.X 版本之后,默认该属性为 true,即默认使用 CGLIB 代理

我们可以观察以下代理工厂的源代码:

createAopProxy 的实现在 DefaultAopProxyFactory
