Spring-AOP
前言
前面的项目啥的都完成得差不多,写这篇文章主要是为了复习,针对面试问题来进行复习总结。
目录
先大致了解一下什么是AOP?
AOP称为面向切面编程, 用于将那些与业务无关的, 却对多个对象产生影响的公共行为和逻辑, 抽取并封装为一个可重用的模块, 这个模块被命名为 " 切面 ", 减少系统中的重复代码, 减低了模块间的耦合度, 同时提高了系统的可维护性.
常见的AOP使用场景:
- 记录操作日志
- 缓存处理
- Spring中内置的事务处理
AOP快速入门:
需求:统计部门管理各个业务层方法执行耗时。
原始方法 : 就是在业务执行之前记录一下开始时间, 执行之后记录结束时间, 最后相减得出执行耗时.

我们可以发现有好多重复的代码, 这里就可以用AOP来实现, 只需要一小段代码.

1). 导入依赖:在 pom.xml 文件中导入 AOP 的依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2). 编写AOP程序:针对于特定方法根据业务需要进行编程
@Component
@Aspect //当前类为切面类
@Slf4j
public class RecordTimeAspect {
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//记录方法执行开始时间
long begin = System.currentTimeMillis();
//执行原始方法
Object result = pjp.proceed();
//记录方法执行结束时间
long end = System.currentTimeMillis();
//计算方法执行耗时
log.info("方法执行耗时: {}毫秒",end-begin);
return result;
}
}
AOP核心构成
简单来说: AOP 的核心构成包括切面(封装横切逻辑)、切点(定义拦截位置)、通知(指定执行动作)、连接点(可拦截的程序执行点)以及织入(将切面融入目标对象的过程)。
拿入门案例来举例:

下面来详细的看一下各个组成部分:
通知:Advice
1. 什么是通知?
指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
- 在入门程序中是需要统计各个业务方法的执行耗时的,此时我们就需要在这些业务方法运行开始之前,先记录这个方法运行的开始时间,在每一个业务方法运行结束的时候,再来记录这个方法运行的结束时间。
- 是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。

常见的通知类型:
常见的AOP操作呢,就是把我们要执行的业务逻辑(目标方法抽离出来再加上一些方法) , 就比如入门案例中在我们的目标方法 ( Object result = pjp.proceed(); ) 前后都加上了方法记录执行时间 , 这就叫做环绕通知 , 但有些不需要在目标方法前后都执行方法, 这就涉及到不同的通知类型. ( 就比如常见的公共字段填充方法, 他就只需要在mapper层执行数据库语句前插入公共字段就可以, 他的通知类型就是前置通知(@Before) )
只要我们在通知方法上加上注解,就可以代表当前的通知类型了。( 比如我们在通知方法上加上了@Around注解,就代表当前通知是一个环绕通知。 )
| @Around | 环绕通知,此注解标注的通知方法在目标方法前、后都被执行 |
|---|---|
| @Before | 前置通知,此注解标注的通知方法在目标方法前被执行 |
| @After | 后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行 |
| @AfterReturning | 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行 |
| @AfterThrowing | 异常后通知,此注解标注的通知方法发生异常后执行 |
@Slf4j
@Component
@Aspect
public class MyAspect1 {
//前置通知
@Before("execution(* com.itheima.service.*.*(..))")
public void before(JoinPoint joinPoint){
log.info("before ...");
}
//环绕通知
@Around("execution(* com.itheima.service.*.*(..))")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
//原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了
log.info("around after ...");
return result;
}
//后置通知
@After("execution(* com.itheima.service.*.*(..))")
public void after(JoinPoint joinPoint){
log.info("after ...");
}
//返回后通知(程序在正常执行的情况下,会执行的后置通知)
@AfterReturning("execution(* com.itheima.service.*.*(..))")
public void afterReturning(JoinPoint joinPoint){
log.info("afterReturning ...");
}
//异常通知(程序在出现异常的情况下,执行的后置通知)
@AfterThrowing("execution(* com.itheima.service.*.*(..))")
public void afterThrowing(JoinPoint joinPoint){
log.info("afterThrowing ...");
}
}
🧩扩展@PointCut注解:
我们发现啊,每一个注解里面都指定了切入点表达式(“execution(* com.itheima.service..(…))”),而且这些切入点表达式都一模一样。此时我们的代码当中就存在了大量的重复性的切入点表达式,假如此时切入点表达式需要变动,就需要将所有的切入点表达式一个一个的来改动,就变得非常繁琐了。
可以使用@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。
@Slf4j
@Component
@Aspect
public class MyAspect1 {
//切入点方法(公共的切入点表达式)
@Pointcut("execution(* com.itheima.service.*.*(..))")
private void pt(){}
//前置通知(引用切入点)
@Before("pt()")
public void before(JoinPoint joinPoint){
log.info("before ...");
}
//环绕通知
@Around("pt()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
log.info("around before ...");
//调用目标对象的原始方法执行
Object result = proceedingJoinPoint.proceed();
//原始方法在执行时:发生异常
//后续代码不在执行
log.info("around after ...");
return result;
}
//后置通知
@After("pt()")
public void after(JoinPoint joinPoint){
log.info("after ...");
}
//返回后通知(程序在正常执行的情况下,会执行的后置通知)
@AfterReturning("pt()")
public void afterReturning(JoinPoint joinPoint){
log.info("afterReturning ...");
}
//异常通知(程序在出现异常的情况下,执行的后置通知)
@AfterThrowing("pt()")
public void afterThrowing(JoinPoint joinPoint){
log.info("afterThrowing ...");
}
}
切入点:PointCut
匹配连接点的条件,通知仅会在切入点方法执行时被应用。
- 在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。
- 在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点。

切入点表达式
首先通过这个入门案例来讲讲这个切入点表达式要注意的点
@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
| 部分 | 含义 |
|---|---|
| execution(…) | 表示匹配方法执行的连接点(Spring AOP 只支持方法级别的连接点) |
| com.itheima.service.impl.DeptServiceImpl | 包路径 com.itheima.service.impl.DeptServiceImpl 是固定包名 |
| *(第一个) | 返回类型:* 表示任意返回类型(包括 void) |
| .* | 方法名:* 表示任意方法名 |
| (…) | 参数列表:… 表示任意类型、任意数量的参数(包括无参) |
- ’ * ':单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分
- ’ … ’ :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数
🙌🙌🙌切入点表达式:描述切入点方法的一种表达式
- 作用:主要用来决定项目中的哪些方法需要加入通知
- 常见形式:
-
- execution(……):根据方法的签名来匹配
-
- @annotation(……) :根据注解匹配
-
execution
execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常?)
其中带?的表示可以省略的部分
- 访问修饰符:可省略(比如: public、protected)
- 包名.类名: 可省略
- throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)
切入点表达式的语法规则:
- 方法的访问修饰符可以省略
- 返回值可以使用*号代替(任意返回值类型)
- 包名可以使用 * 号代替,代表任意包(一层包使用一个*)
- 使用…配置包名,标识此包以及此包下的所有子包
- 类名可以使用*号代替,标识任意类
- 方法名可以使用*号代替,表示任意方法
- 可以使用 * 配置参数,一个任意类型的参数
- 可以使用… 配置参数,任意个任意类型的参数
切面:Aspect,描述通知与切入点的对应关系(通知+切入点)
当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。

小白啊!!!写的不好轻喷啊🤯如果觉得写的不好,点个赞吧🤪(批评是我写作的动力)
…。。。。。。。。。。。…
…。。。。。。。。。。。…
更多推荐




所有评论(0)