Spring AOP 与 AOP 就如果 IOC(思想:控制反转) 和 DI (具体实现:依赖注入) ,其中AOP是思想,Spring AOP是具体实现。
AOP 是⼀种思想, Spring AOP 是⼀个框架,提供了⼀种对 AOP 思想的实现。
AOP(Aspect Oriented Programming):⾯向切⾯编程,它是⼀种思想,它是对某⼀类事情的集中处理。
AOP:面向切面编程,与OOP(面向对象编程)类似但又不互斥,可以说AOP是OOP的一种拓展与补充。
⽐如⽤户登录权限的效验,没用 AOP 之前,我们所有需要判断⽤户登录的⻚⾯(中的⽅法),都要各⾃实现或调⽤⽤户验证的⽅法,然⽽有了 AOP 之后,我们只需要在某⼀处配置⼀下,所有需要判断⽤户登录⻚⾯(中的⽅法)就全部可以实现用户登录验证了,不再需要每个⽅法中都写相同的⽤户登录验证了。
如在判断用户是否登录的场景中,一个网站项目中,除了登录和注册这两个页面不用判断用户登录状态,此外的诸多页面都需要使用同样的方式来判断用户是否登录,这些判断方法都是相同的,这么多的方法就会增加代码的维护和修改的成本;
对于这种功能的统一,且使用的地方较多的功能,就可以考虑 AOP 来进行统一处理了。
除了统⼀的⽤户登录判断之外,AOP 还可以实现:
也就是说使⽤ AOP 可以扩充多个对象的某个能⼒,所以 AOP 可以说是 OOP(Object OrientedProgramming,⾯向对象编程)的补充和完善。
AOP 组成的官方概念很难理解,这里给出官网解释(不加粗)和博主个人理解解释(加粗)
切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。
切面:定义 AOP 是针对哪个统一的功能的,这个功能就叫做一个切面,比如用户登录功能或方法的统计日志,他们就各是一个切面。切面是由 切点 +通知 组成的。切⾯是包含了:通知、切点和切⾯的类,相当于 AOP 实现的某个功能的集合。
应⽤执⾏过程中能够插⼊切⾯的⼀个点,这个点可以是⽅法调⽤时,抛出异常时,甚⾄修改字段时。切⾯代码可以利⽤这些点插⼊到应⽤的正常流程之中,并添加新的⾏为。
连接点:所有可能触发 AOP (拦截方法的点) 就称为连接点;连接点相当于需要被增强的某个 AOP 功能的所有方法。
Pointcut 是匹配 Join Point 的谓词。
Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来匹配 Join Point,给满⾜规则的 Join Point 添加 Advice。
切点:用来定义 AOP 拦截的规则的 ;切点相当于保存了众多连接点的⼀个集合(如果把切点看成⼀个表,而连接点就是表中⼀条⼀条的数据)。
切⾯也是有⽬标的 ——它必须完成的⼯作。在 AOP 术语中,切⾯的⼯作被称之为通知。
通知:定义了切⾯是什么,何时使⽤,其描述了切⾯要完成的⼯作,还解决何时执⾏这个⼯作的问题。
通知:规定 AOP 执行的时机和执行的方法;
Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
前置通知使用 @Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
后置通知使用 @After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
返回之后通知使用 @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
抛异常后通知使用 @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调用之后执行自定义的行为。
通知相当于要增强的方法
AOP 整个组成部分的概念如下图所示,以多个页面都要访问⽤户登录权限为例:
这节博主用 Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。
实现步骤如下:
在 maven 中央仓库中搜索 spring aop,,选择如图所示第二个:
选择一个次新版本(考虑稳定),点进去:
在 pom.xml 中添加如下配置:
!-- https://mvnrepository.com/artifact/org.springframework.boot/springboot-starter-aop -->
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-aopartifactId>
dependency>
切面指的是具体要处理的某⼀类问题,⽐如⽤户登录权限验证就是⼀个具体的问题;
Spring AOP 切面的定义如下,在切面中我们要定义拦截的规则(切点),具体实现如下:
这里切点的定义使用的是 AspectJ 表达式语法
其中 pointcut ⽅法为空⽅法,它不需要有⽅法体,此⽅法名就是起到⼀个“标识”的作⽤,标识下⾯的通知⽅法具体指的是哪个切点(因为切点可能有很多个)。
AspectJ ⽀持三种通配符:
*:表示匹配任意的内容,用在返回值,包名,类名,方法名都可以使用
…:匹配任意字符,可以使用在方法参数上,如果用在类上需要配合 * 一起使用
+:表示匹配指定类及其它底下的所有子类,比如 com.Car+ 表示匹配Car 及其所有的子类
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法为:
execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
(修饰符和异常可以省略)
通知定义的是被拦截的⽅法具体要执⾏的业务,⽐如⽤户登录权限验证⽅法就是具体要执⾏的业务。
Spring AOP 中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
具体实现如下:
usercontroller 类下方法如下:
前置通知:
后置通知:
在设置了前置通知和后置通知的情况下,访问方法:
可以看出,前置通知与后置通知分别在方法执行之前和之后进行执行。
可以看出 先执行前置通知,在方法抛出异常后,抛异常后通知@AfterThrowing 执行,接着再执行后置通知;
先执行前置通知,在方法正常返回后,返回之后通知 @AfterReturning执行,接着再执行后置通知;
环绕通知博主以实现 记录方法所执行的时间 的需求来举列:
具体实现细节如下:
加上前面的调整,访问方法结果如下:
使用以上的注解就能实现 spring AOP 啦 ~
Spring AOP 是通过动态代理的⽅式,在运行期将 AOP 代码织入到程序中的,它的实现⽅式有两种:JDK Proxy 和 CGLIB,这两种实现动态代理的方式均使用到了 反射 的知识。
Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截。(功能的最小实现就是方法)
Spring AOP ⽀持 JDK Proxy 和 CGLIB Proxy ⽅式实现动态代理。
默认情况下,实现了接⼝的类,使⽤AOP 会基于 JDK ⽣成代理类,没有实现接⼝的类,会基于 CGLIB ⽣成代理类。
织⼊是把切⾯应⽤到⽬标对象并创建新的代理对象的过程,切⾯在指定的连接点被织⼊到⽬标对象中。
在⽬标对象的⽣命周期⾥有多个点可以进⾏织⼊:
此种实现在设计模式上称为动态代理模式,在实现的技术⼿段上,都是在 class 代码运⾏期,动态的织⼊字节码。
Spring AOP,主要基于两种⽅式:JDK 及 CGLIB 的⽅式。这两种⽅式的代理⽬标都是被代理类中的⽅法,在运⾏期,动态的织⼊字节码⽣成代理类。
InvocationHandler
及 Proxy,在运⾏时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完成。