面试遇到SpringAOP不要慌
Aspect Oriented Programming(面向切面编程)是一种编程思想。所谓切面可以理解为特定的一些方法。所以AOP可以看成是面向特定的方法进行编程。比如上一篇文章提到的拦截器,就是AOP的体现。它针对特定的方法进行统一的管理,让代码模块化,易于维护。包括ExceptionAdvice、ResponseBodyAdvice(统一异常处理、统一数据返回格式)都运用了AOP的思想。定义一
- 📌 博主简介: 💻 努力学习的 23 级科班生一枚 🚀
- 🏠 博主主页 : 📎 @灰阳阳
- 📚 往期回顾 :Spring拦截器(Intercepter)
- 每日一言 :如果你认定人的一生只能活一次,那你就没有随波逐流的理由。✅
文章目录
一、什么是AOP
Aspect Oriented Programming(面向切面编程)是一种编程思想。所谓切面可以理解为特定的一些方法。所以AOP可以看成是面向特定的方法进行编程。比如上一篇文章提到的拦截器,就是AOP的体现。它针对特定的方法进行统一的管理,让代码模块化,易于维护。 包括ExceptionAdvice、ResponseBodyAdvice(统一异常处理、统一数据返回格式)都运用了AOP的思想。
二、SrpingAOP的相关概念
使用Spring框架进行AOP开发一般有两个方式:
- 注解实现
- XML配置实现
并且需要实现导入SpingAOP依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
在讲解AOP(面向切面编程)中的这些概念之前,我们先理解一下AOP的目的是什么。在软件开发中,有些功能(比如日志记录、安全检查、事务管理等)会散落在程序的各个模块中,这些功能被称为“横切关注点”。AOP就是为了将这些横切关注点从核心业务逻辑中分离出来,让代码更清晰、更易于维护。
现在,我们来分别解释一下这些核心概念:
-
连接点(Join Point)
- 通俗理解: 程序执行过程中,那些“可以被增强”的特定点。可以想象成你程序运行时的“某个瞬间”或“某个地方”。
- 详细解释: 连接点是指在应用程序执行过程中,可以插入切面(Aspect)逻辑的任何点。这包括但不限于方法的调用、方法的执行、异常的处理、字段的访问等。 Spring AOP主要支持方法的执行作为连接点。
- 例子: 当你调用一个
userService.createUser()方法时,在方法执行前、方法执行后、方法抛出异常时,这些都可以是连接点。
-
切点(Pointcut)
- 通俗理解: 一种“规则”或“条件”,用来精确地选择出你想要增强的那些“连接点”。
- 详细解释: 切点是一个表达式,它定义了哪些连接点应该被“通知”(Advice)执行。简单来说,它就像一个过滤器,从所有可能的连接点中筛选出你真正感兴趣的那些点。 例如,你可以定义一个切点,表示“所有以
Service结尾的类中的所有公共方法”都是我想要增强的连接点。 - 例子: 你想在所有用户管理相关的方法执行前打印日志,那么你的切点就可以定义为“所有
com.example.service.user包下所有方法的执行”。
-
通知(Advice)
- 通俗理解: 在选定的“连接点”上执行的“具体操作”或“增强逻辑”。它定义了“做什么”以及“何时做”。
- 详细解释: 通知是切面在特定连接点执行的动作。它包含了实际的横切关注点逻辑,比如记录日志的代码、安全检查的代码、事务开始/提交/回滚的代码等。
- 常见类型:
- 前置通知(@Before): 在连接点执行之前运行,但不能阻止连接点继续执行。
- 后置通知(@After): 无论连接点如何退出(正常返回或抛出异常),都会在连接点执行之后运行。
- 返回通知(@AfterReturning): 在连接点正常成功执行并返回结果之后运行。
- 异常通知(@AfterThrowing): 在连接点抛出异常之后运行。
- 环绕通知(@Around): 包裹住连接点,可以在连接点执行前和执行后都进行操作,甚至可以控制连接点是否执行、修改返回值或参数。
-
切面(Aspect)
- 通俗理解: 一个“模块”,它将“切点”和“通知”打包在一起。 可以理解为一个横切关注点的具体实现。
- 详细解释: 切面是横切关注点的模块化单元。它通常是一个类,用特定的注解(如Spring中的
@Aspect)标识,它包含了通知(定义了要做什么)和切点(定义了在哪里做)。 一个切面可以包含多个通知和切点。 - 例子: 你可以创建一个名为
LoggingAspect的切面,它里面定义了一个切点(例如:所有业务层方法),以及一个前置通知(在方法执行前打印日志)和一个后置通知(在方法执行后打印日志)。
总结一下它们之间的关系:
你可以把你的程序想象成一条生产线,上面有很多工作站(连接点)。你想要在某些特定的工作站(通过切点规则筛选出来的连接点)进行额外的操作(通知)。那么,将这些“在哪儿操作”(切点)和“具体操作什么”(通知)打包在一起,就是一个“切面”。 AOP框架会在程序运行时,根据切面中定义的切点规则,在相应的连接点上执行通知中定义的增强逻辑,从而实现对核心业务逻辑的非侵入式增强。
1. 切点(PointCut)
代码中demo.controller路径下的所有类的方法都会统一执行下面的方法, "execution(* com.example.demo.controller..*.*(..))"(切点表达式)就是一个切点,用于描述那些方法需要进行增强:
@Aspect
@Component
public class TimeLogAspect {
private static final Logger log = LoggerFactory.getLogger(TimeLogAspect.class);
@Around("execution(* com.example.demo.controller..*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
//.......
}
}
2. 连接点(Join Point)
demo.controller 路径下的某一个具体的方法指的就是连接点(addBook、queryBookById等):
package com.example.demo.controller;
import org.springframework.web.bind.annotation.*;
import com.example.demo.entity.BookInfo;
import com.example.demo.common.Result;
@RestController
@RequestMapping("/book")
public class BookController {
@RequestMapping("/addBook")
public Result addBook(@RequestBody BookInfo bookInfo) {
//...代码省略
return Result.success();
}
@RequestMapping("/queryBookById")
public Result queryBookById(@RequestParam Integer bookId) {
//...代码省略
return Result.success();
}
@RequestMapping("/updateBook")
public Result updateBook(@RequestBody BookInfo bookInfo) {
//...代码省略
return Result.success();
}
}
切点和连接点的关系:
连接点是满足切点表达式(@Around括号的内的字符串)的元素,切点是多个满足条件的连接点的集合。例如:
切点表达式:共青团
连接点:共青团中的某个共青团团员
3. 通知(Advice)
AOP需要从多个不同的业务代码中抽离出一些指定的有重复性的代码,这些重复代码就是通知的内容。
比如下面这个统计某个业务执行时间的代码就是通知:
@Around("execution(* com.example.demo.controller..*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 记录方法执行开始时间
long begin = System.currentTimeMillis();
// 执行原始方法
Object result = pjp.proceed();
// 记录方法执行结束时间
long end = System.currentTimeMillis();
// 记录方法执行耗时
log.info(pjp.getSignature() + " 执行耗时: {} ms", end - begin);
return result;
}
4.切面(Aspect)
切面(Aspect) = 通知(Advice) + 切点(PointCut)
如下代码,TimeAspect类中的整个方法(recordTime)就是一个切面。切点通过@Round注解设置,通知就是时间的记录:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
/**
* 记录方法耗时的切面
*/
@Aspect
@Component
public class TimeAspect {
/**
* 记录方法耗时
*/
@Around("execution(* com.example.demo.controller..*.*(..))")
public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
// 记录方法执行开始时间
long begin = System.currentTimeMillis();
// 执行原始方法
Object result = pjp.proceed();
// 记录方法执行结束时间
long end = System.currentTimeMillis();
// 记录方法执行耗时
log.info(pjp.getSignature() + " 执行耗时: {} ms", end - begin);
return result;
}
}
此外含有切面的类,我们称这个类角坐切面类,这个类会被@Aspect修饰。
三、通知类型
第二节中的代码演示中@Round是其中通知注解,在Spring中通知类型有以下几种
@Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行@Before:前置通知,此注解标注的通知方法在目标方法前被执行@After:后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行@AfterReturning:返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行@AfterThrowing:异常后通知,此注解标注的通知方法在发生异常后执行
方法没有发生异常时,运行顺序:

方法发生异常时,运行顺序:
因为程序发生异常,所以AfterReturning和Around后不会执行.。一般@Round用的比较多,因为这个注解可以模拟实现其他注解的功能(执行时机)
注意事项:
@Round注解修饰的方法必须返回目标方法的返回值,如果目标方法没有返回值,返回null?
四、自定义注解实现AOP
- 定义一个注解:
@Retention(RetentionPolicy.RUNTIME)//表示运行时,仍然存在
@Target({ElementType.METHOD})//表示这个注解只能修饰方法
public @interface TimeRecord {
}
- 把这个注解申明到一个方法中(需要用到@annotation):
@Slf4j
@Aspect
@Component
public class TimeRecordAspect {
@Around("@annotation(com.bite.aop.aspect.TimeRecord)")
//@Around("@annotation(org.springframework.web.bind.annotation.RequestMapping)")//@annotation可以用到spring原生的注解中
public Object timeRecord(ProceedingJoinPoint pjt){
//1. 记录开始时间
//2. 执行目标代码
//3. 记录结束时间
//4. 返回结果
log.info("TimeRecordAspect 前");
long start = System.currentTimeMillis();
//执行目标方法
Object o = null;
try {
o = pjt.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("TimeRecordAspect 后");
log.info(pjt.getSignature()+ "耗时: "+ (System.currentTimeMillis()-start) + "ms");
return o;
}
}
- 使用自定义的@TimeRecord注解
@Slf4j
@RequestMapping("/test")
@RestController
public class TestController {
@TimeRecord//这是我们自己定义的注解
@RequestMapping("/t1")
public String t1(){//此方法将会被AOP代理,记录执行时间
log.info("执行T1方法");
return "t1";
}
}
五、SpringAOP的实现原理
SpringAOP是通过动态代理对AOP编程思想的实现。
主要由两种方式实现:
- JDK动态代理
- CGlib动态代理。
代理模式: 代理模式是一种常见的设计模式。设计一个代理类,这个代理类可以控制目标对象的创建已经此对象目标方法的调用,这种简介调用的方式就是代理模式。
形象的解释: 比如我们想在APP上找房子租,APP客服就是一个租房代理,他联系各个房东看看有没有满足我们条件的房子,如果有,就把信息给我们,我们就可以去租。虽然他不是房产的拥有人,但是他帮我们成功租到了房子,这就是代理。
动态代理和静态代理的区别: 静态代理意思是在程序运行前,代理对象(.class文件)经已经创建了,动态代理是在程序运行时自动创建的(程序运行前并没有这个.class文件)
1. JDK动态代理
JDK动态代理运用的是Java的反射机制,在程序运行时创建代理对象,从而实现动态代理。但是它只能代理实现接口的类。 原因在于反射的一个核心方法的参数中需要传递目标类的接口数组:target.getClass().getInterfaces(),对于没有实现任何接口的目标类,这个参数无法传递。
2. CGlib动态代理
Spring会自动引入CGlib相关依赖。其动态代理是基于继承实现的,创建目标类的一个子类把增强逻辑植入子类。因此不能代理被final修饰的类或者方法,性能也要比原生的JDK低一些。但是其优点是可以代理没有实现任何接口的子类。
Spring Boot 2.x之后的版本统一使用了CGlib进行动态代理
Spring Frameword 有实现接口的类,用JDK动态代理实现,没有实现接口的类,用CGlib实现动态代理
更多推荐



所有评论(0)