目录
Spring AOP
编辑AOP适用场景
AOP的组成
连接点(Join Point)
切点(Pointcut)
通知(Advice)
Spring AOP的实现
添加依赖
定义切面与切点
切点表达式的说明
定义相关的通知
Spring AOP
AOP(Aspect Oriented Programming)是面向切面编程,是一种设计思想.对某一类的事情集中处理
就像是在页面需要在用户为登录状态这个前提下才能进行后序的操作,一个项目肯定不止有一个页面,那么对于每一个不同页面的方法我们都要去加入判断用户登录状态的程序,在后期维护和修改的时候花费的成本会比较大.
对于这些需要去完成同一件事的不同的方法/类,我们就可以把他们集中起来进行配置处理.
AOP适用场景
适用于对于功能统一且被运用比较多的就可以考虑用AOP进行统一的处理.
1.页面登录状态的验证
2.统一的日志记录
3.统一的方法执行时间的计算
...
AOP就像给某一个方法/类在特定的位置新增添了一些功能.
但这些功能是我们程序员本来就想让他们实现的,但是每一个方法都实现花费的成本就比较多,所以集中起来进行统一的配置.
AOP的组成
AOP中有四个关键词:切面(Aspect),切点(Pointcut),连接点(Join Point)以及通知(Advice)
下面对四个关键词进行展开,并简单说明AOP的流程.
连接点(Join Point)
连接点是应用执行过程中能够插入切面的一个点.这个点可以是方法中的开头,抛出异常时或方法结束的之前或之后.
切点(Pointcut)
切点指的是通知要织入的具体位置.
(切点与连接点的关系图)
通知(Advice)
切面的工作被称为了通知.通知定义了切面的工作是什么,以及要工作的时间节点.
通知有着五种类型:
- 前置通知(Before):在目标方法被调用之前调用通知功能
- 后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么
- 返回通知(After-returning):在目标方法成功执行之后调用通知
- 异常通知(After-throwing):在目标方法抛出异常后调用通知
- 环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和之后执行自定义的行为
切面(Aspect)
切面是通知与切点的集合.其包含了工作的内容,执行的地点与执行的时间.
Spring AOP的实现
添加依赖
在Maven仓库中搜索AOP,选择
Maven Repository: org.springframework.boot » spring-boot-starter-aop (mvnrepository.com)
此处我选择的是2.7.x的版本
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.8</version>
</dependency>
定义切面与切点
在与启动类的同一个目录下,创建AOP包并创建一个对应的类,我们会将这个类定义为"切面"
@Aspect //切面的定义
@Component//还是要把此类注入到spring boot当中
public class UserAOP {
//切点的定义
//AspectJ表达式,来规定切点的范围
@Pointcut("execution(* com.example.demo.Controller.UserController.* (..))")
public void pointcut(){}
@Pointcut()
public void abcd(){} //@Pointcut注解下的方法都是无具体实现的空方法,作为@Pointcut标签的"载体"
@Before("pointcut()")//通知
public void doBefore(){
System.out.println("前置通知");
}
}
@Pointcut修饰的方法都不需要具体实现,只是作为标识的存在.因为切点可以很多个,就可以用被注解的方法名来区分并调用.
切点表达式的说明
AspectJ表达式支持三种通配符
- * :匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
- .. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
- + :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,语法:
execution(<修饰符><返回类型><包.类.⽅法(参数)><异常>)
一般修饰符与异常两个参数可以省略.
修饰符:
- public,匹配修饰符为public的
- * ,任意
返回类型:
- void,匹配返回类型为void的
- *,任意
包(包的路径):
- com.example.demo,在此包下的
- com.example.demo.*.controller,在demo子包下的任意的controller包下(例如在,前面省略demo.aop.controller)
- com.example.demo..(两个"."),在demo下面的任意子包即自己
类:
- UserAOP,指定的固定类
- *AOP,指定以AOP结尾的类
- User*,指定以User开头的类
- *,任意
方法名:
- addUser,指定方法
- *User,以User结尾的方法
- add*,以add为开头的方法
- *,任意
参数:
- (),指定无参的方法
- (int),指定只有一个参数,且参数为int的方法
- (int,String),指定有两个参数,且第一个为int,第二个为String的方法
- (..),参数任意
示例:
"execution(* com.example.demo.Controller.UserController.* (..))":匹配对应Controller包中UserController类下的所有方法都可以作为切点.
定义相关的通知
通知的定义是被拦截的方法具体要执行的业务.像是前面所说的登录验证,在切点范围中的方法将要执行之前会被拦截下来,先进行登录验证.这个登录验证就是具体要执行的业务.
@Before("pointcut()")
public void doBefore(){
System.out.println("前置通知");
}
@After("pointcut()")
public void doAfter(){
System.out.println("后置通知");
}
@AfterReturning("pointcut()")
public void doAfterReturn(){
System.out.println("方法返回前发送的通知");
}
@AfterThrowing("pointcut()")
public void doAfterThrow(){
System.out.println("方法抛出异常前发送的通知");
}
//以上方法都是Around下的注解,Around更像是自定义模式,指定通知在哪里执行.
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
Object result = null;
System.out.println("哇哈哈哈");
result = proceedingJoinPoint.proceed();//调用被拦截的方法
System.out.println("wahahaha");
return result;
}
关于环绕通知的几个注意点:
- 使用Object作为返回值,相比于前面几个类型的通知.环绕通知显然更灵活一些,其可以选择调用被拦截的方法,也可以选择不调用而自身根据某些条件直接返回.但如果进行了调用,对于拦截的方法由于是动态的,在执行之前我们并不知道方法的返回值是什么只能使用Object类接收.
- 要含有ProceedingJoinPoint参数,在这个类的对象下可以调用proceed方法来启动被拦截下的方法.
- 其他通知只是在原来方法的基础上进行增添功能,而环绕通知则可以直接决定方法是否被进行调用.