一、了解Spring AOP
1.什么是Spring AOP
面向切面编程,是一种思想,它是对某一类事情进行集中处理。
举个例子:比如用户登录权限的校验。
不用AOP:需要判断用户登录的界面,都需要各自实现或调用用户验证的方法。
用AOP:只需要配置一处方法,就可以供所有需要判定用户登录页面中的方法全部实现登录验证,不需要繁琐的每个方法写相同的登录验证。
再贴切的例子就是:五个室友点了 五份相同的外卖,外卖同一时间放在同一个地点。
不用AOP:五个室友同时过去,一个一个拿外卖,耗费资源和时间,
用AOP:派一个代拿,直接把五个外卖全部拿回来。节省资源和时间。
小结:AOP 是⼀种思想,⽽ Spring AOP 是⼀个框架,提供了⼀种对 AOP 思想的实现,它们的关系和IoC 与 DI 类似
二、了解 Spring AOP的构成
2.1 切面(Aspect)
a.切⾯(Aspect)由切点(Pointcut)和通知(Advice)组成,它既包含了横切逻辑的定义,也包括了连接点的定义。
b.切⾯是包含了:通知、切点和切⾯的类,相当于 AOP 实现的某个功能的集合
2.2 连接点(Join Point)
应用执行过程中,能够插入切面的一个点,这个点在方法调用时,抛出异常时,甚至修改字段时,切面代码可以利用这些点插入到应用的正常流程中,并添加新的行为。
连接点相当于需要被增强的某个 AOP 功能的所有⽅法。
2.3 切点(Pointcut)
提供一组规则来匹配 Join Point,给满足Join Point 添加 Advice
切点相当于保存了众多连接点的一个集合。
如果把切点看成一个表,连接点就是表中一条一条的数据。
2.4 通知(Advice)
切⾯也是有⽬标的 ——它必须完成的⼯作。在 AOP 术语中,切⾯的⼯作被称之为通知。
通知:定义了切面是什么,何时使用,描述切面要完成的工作,还解决何时执行这个工作的问题。
Spring切面类中,可以在方法上使用以下注释,会设置方法为通知方法,在满足条件后通知本方法进行调用
前置通知使用@Before:通知方法会在目标方法调用之间执行
后置方法使用@After:通知方法会在目标方法返回或者抛出异常后调用
返回之后通知使⽤ @AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
抛异常后通知使⽤ @AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
环绕通知使⽤ @Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为
AOP整个组成部分的概念如下图所示,以多个页面需要访问用户登录权限为例:
三、Spring AOP的实现
Spring AOP 的实现步骤:1.添加Spring AOP框架支持 2.定义切面与切点 3.定义通知
3.1 添加 AOP框架支持
在pom.xml中添加如下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.2 定义切面与切点
切点指的是具体要处理的某⼀类问题,⽐如⽤户登录权限验证就是⼀个具体的问题,记录所有⽅法的执⾏⽇志就是⼀个具体的问题,切点定义的是某⼀类问题。
Spring AOP 切点定义如下: 在切点中,要定义 拦截的规则。具体实现如下:
@Aspect//表明此类为一个切点
@Component
public class UserAspect {
//定义切点,
//这里使用AspectJ 表达式 语法
@Pointcut("execution(* com.example.springdemo5.AOPTest.*(..))")
public void pointcut(){}
}
3.3定义通知并执行
//前置通知
@Before("pointcut()")
public void doBefore(){
System.out.println("执行了前置通知");
}
//后置通知
@After("pointcut()")
public void doAfter(){
System.out.println("执行了后置通知");
}
//环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕通知执行之前");
// 执行目标方法
Object result = joinPoint.proceed();
System.out.println("环绕通知执行之后");
return result;
}
3.4 定义相关通知
通知定义的是被拦截的⽅法具体要执⾏的业务,⽐如⽤户登录权限验证⽅法就是具体要执⾏的业务。
Spring AOP 中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本⽅法进⾏调⽤:
前置通知使⽤@Before:通知⽅法会在⽬标⽅法调⽤之前执⾏。
后置通知使⽤@After:通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
返回之后通知使⽤@AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
抛异常后通知使⽤@AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
环绕通知使⽤@Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为
@Aspect//定义切面
@Component
public class UserAspect {
//切点
@Pointcut("execution(* com.example.springdemo5.controller.UserController.*(..))")
public void pointcut(){}
//前置通知
@Before("pointcut()")
public void doBefore(){
System.out.println("执行了前置通知");
}
//后置通知
@After("pointcut()")
public void doAfter(){
System.out.println("执行了后置通知");
}
//return 之前通知
@AfterReturning("pointcut()")
public void doAfterReturning(){
System.out.println("执行了 AfterReturning方法");
}
//抛出异常之前通知
@AfterThrowing("pointcut()")
public void doAfterThrowing(){
System.out.println("执行了 AfterThrowing 方法");
}
//环绕通知
@Around("pointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("环绕通知执行之前");
// 执行目标方法
Object result = joinPoint.proceed();
System.out.println("环绕通知执行之后");
return result;
}
}
四、AspectJ ⽀持三种通配符语法
*:匹配任意字符,只匹配⼀个元素(包,类,或⽅法,⽅法参数)
.. :匹配任意字符,可以匹配多个元素 ,在表示类时,必须和 * 联合使⽤。
+ :表示按照类型匹配指定类的所有类,必须跟在类名后⾯,如 com.cad.Car+ ,表示继承该类的所有⼦类包括本身
切点表达式由切点函数组成,其中 execution() 是最常⽤的切点函数,⽤来匹配⽅法,
语法为: 定义切⾯和切点切点表达式说明execution()
4.1修饰符的具体含义
修饰符和异常可以省略,具体含义如下:
修饰符,一般省略: public 公共方法 * 任意
返回值,不能省略: void 返回没有值,String 返回字符串, * 任意
包:
com.demo | 固定包 |
com.demo.*.service | demo包下的子包任意 |
com.demo | demo包下的所有子包(包括自己) |
com.demo.*.service.. | demo包下面任意子包,固定目录service, service目录任意包 |
类:
UserServiceImpl | 指定类 |
*Impl | 以lmpl结尾 |
User* | 以User开头 |
* | 任意 |
方法名,不可忽略:
addUser | 固定方法 |
add* | 以add开头 |
*Do | 以Do结尾 |
* | 任意 |
参数:
() | 无参 |
(int) | 一个整型 |
(int,int) | 两个 |
(..) | 参数任意 |
throws,可省略
4.2 表达式示例
execution(* com.cad.demo.User.*(..)) :匹配 User 类⾥的所有⽅法。
execution(* com.cad.demo.User+.*(..)) :匹配该类的⼦类包括该类的所有⽅法。
execution(* com.cad.*.*(..)) :匹配 com.cad 包下的所有类的所有⽅法。
execution(* com.cad..*.*(..)) :匹配 com.cad 包下、⼦孙包下所有类的所有⽅法。
execution(* addUser(String, int)) :匹配 addUser ⽅法,且第⼀个参数类型是 String,第⼆个参数类型是 int。