Spring AOP 深度解密:从动态代理原理到实战坑点全解
作为 Java 后端开发者,你一定用过 Spring 的事务管理、日志记录、权限校验这些功能。但你有没有想过:为什么只需要加一个@Transactional注解,就能自动实现事务?为什么不需要在每个业务方法中都写重复的日志代码?
这背后的核心就是AOP(面向切面编程)。AOP 是 Spring 的三大核心之一(另外两个是 IoC 和事务),它通过横向抽取重复代码,实现了业务逻辑与非业务逻辑的解耦,是 Spring 中最优雅也最容易被误解的特性。
面试时,AOP 更是 100% 的必考题,面试官会从原理到实践层层深挖:
- AOP 的核心概念是什么?切面、连接点、通知分别指什么?
- JDK 动态代理和 CGLIB 动态代理有什么区别?
- 为什么同一个类中方法调用 AOP 不生效?怎么解决?
- AOP 不生效的常见场景有哪些?
- 如何控制多个切面的执行顺序?
- AOP 会影响性能吗?
这篇文章,我们就从核心概念→底层原理→执行流程→实战应用→面试坑点五个维度,彻底搞懂 Spring AOP。不仅会讲清楚理论,更会提供可直接落地的代码示例和避坑指南,让你看完既能轻松应对面试,又能解决实际项目中的 AOP 问题。

一、先搞懂:什么是 AOP?
1. AOP 解决了什么问题?
在传统的面向对象编程(OOP)中,我们通过类的继承和组合来实现代码复用。但对于一些横切关注点(Cross-cutting Concerns),比如日志记录、性能监控、事务管理、权限校验等,OOP 就显得力不从心了。
这些横切关注点有两个特点:
- 分散在多个业务方法中:几乎每个业务方法都需要记录日志、校验权限
- 与核心业务逻辑无关:日志、事务这些功能不影响核心业务逻辑,但又必不可少
如果用 OOP 的方式实现,我们需要在每个业务方法中都重复编写这些代码,导致代码冗余、耦合度高、难以维护。
而 AOP 的出现完美解决了这个问题。它将这些横切关注点从业务逻辑中抽取出来,封装成独立的切面,然后在运行时动态地将切面代码织入到业务方法中。这样,业务方法只需要关注核心业务逻辑,不需要关心日志、事务这些通用功能。
2. 一个例子看懂 AOP
我们用最常见的日志记录场景来对比 OOP 和 AOP 的区别:
OOP 方式(代码冗余)
@Service
public class UserService {
public void createUser(User user) {
// 日志记录
System.out.println("开始执行createUser方法,参数:" + user);
long start = System.currentTimeMillis();
try {
// 核心业务逻辑
userMapper.insert(user);
System.out.println("createUser方法执行成功");
} catch (Exception e) {
System.out.println("createUser方法执行失败:" + e.getMessage());
throw e;
} finally {
long end = System.currentTimeMillis();
System.out.println("createUser方法执行耗时:" + (end - start) + "ms");
}
}
public void updateUser(User user) {
// 同样的日志代码,重复编写
System.out.println("开始执行updateUser方法,参数:" + user);
long start = System.currentTimeMillis();
try {
userMapper.update(user);
System.out.println("updateUser方法执行成功");
} catch (Exception e) {
System.out.println("updateUser方法执行失败:" + e.getMessage());
throw e;
} finally {
long end = System.currentTimeMillis();
System.out.println("updateUser方法执行耗时:" + (end - start) + "ms");
}
}
}
AOP 方式(代码解耦)
// 日志切面,只需要编写一次
@Aspect
@Component
public class LogAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("开始执行" + methodName + "方法,参数:" + Arrays.toString(args));
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // 执行原方法
System.out.println(methodName + "方法执行成功");
return result;
} catch (Exception e) {
System.out.println(methodName + "方法执行失败:" + e.getMessage());
throw e;
} finally {
long end = System.currentTimeMillis();
System.out.println(methodName + "方法执行耗时:" + (end - start) + "ms");
}
}
}
// 业务方法,只关注核心逻辑
@Service
public class UserService {
public void createUser(User user) {
userMapper.insert(user);
}
public void updateUser(User user) {
userMapper.update(user);
}
}
可以看到,AOP 方式将日志代码从业务方法中完全抽取出来,业务方法变得非常简洁。如果需要修改日志逻辑,只需要修改LogAspect一个地方即可,不需要修改所有业务方法。
二、AOP 核心概念(面试必背)
AOP 有几个非常重要的核心概念,很多人学了很久 AOP 还是搞不清这些概念的区别。我用最通俗的语言和表格来解释它们:
| 概念 | 英文 | 定义 | 通俗理解 |
|---|---|---|---|
| 切面 | Aspect | 横切关注点的模块化,包含通知和切点 | 封装了通用功能的类,比如日志切面、事务切面 |
| 连接点 | Join Point | 程序执行过程中的某个点,比如方法调用、异常抛出 | 所有可以被切面增强的方法 |
| 切点 | Pointcut | 匹配连接点的表达式,用于指定哪些连接点需要被增强 | 筛选条件,决定哪些方法需要被切面处理 |
| 通知 | Advice | 切面在特定连接点上执行的动作,也就是切面的具体代码 | 切面要执行的具体逻辑,比如前置通知、后置通知 |
| 目标对象 | Target | 被一个或多个切面增强的对象 | 原始的业务对象 |
| 代理对象 | Proxy | AOP 框架生成的对象,包含了目标对象的所有方法和切面逻辑 | Spring 生成的代理对象,我们实际调用的是这个对象 |
| 织入 | Weaving | 将切面应用到目标对象并创建代理对象的过程 | 把切面代码插入到业务方法中的过程 |
五种通知类型
Spring AOP 提供了五种通知类型,分别在连接点的不同时机执行:
| 通知类型 | 注解 | 执行时机 |
|---|---|---|
| 前置通知 | @Before |
在目标方法执行之前执行 |
| 后置通知 | @After |
在目标方法执行之后执行(无论是否抛出异常) |
| 返回通知 | @AfterReturning |
在目标方法正常返回之后执行 |
| 异常通知 | @AfterThrowing |
在目标方法抛出异常之后执行 |
| 环绕通知 | @Around |
在目标方法执行前后都执行,可以控制目标方法是否执行 |
注意:环绕通知是功能最强大的通知,它可以完全控制目标方法的执行,但也是最容易出错的通知。如果忘记调用joinPoint.proceed(),会导致原方法不执行。
三、AOP 底层原理:动态代理
Spring AOP 的底层是基于动态代理实现的。动态代理是指在运行时动态生成代理对象,不需要手动编写代理类。Spring 提供了两种动态代理实现:JDK 动态代理和CGLIB 动态代理。
1. JDK 动态代理
JDK 动态代理是 Java 原生提供的动态代理机制,它基于接口实现。
实现原理
- 定义一个业务接口
- 编写目标对象实现这个接口
- 编写一个调用处理器实现
InvocationHandler接口,在invoke方法中编写切面逻辑 - 调用
Proxy.newProxyInstance()方法生成代理对象
代码示例
// 业务接口
public interface UserService {
void createUser(User user);
}
// 目标对象
public class UserServiceImpl implements UserService {
@Override
public void createUser(User user) {
System.out.println("创建用户:" + user);
}
}
// 调用处理器
public class LogInvocationHandler implements InvocationHandler {
private final Object target;
public LogInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置通知
System.out.println("开始执行" + method.getName() + "方法");
// 执行目标方法
Object result = method.invoke(target, args);
// 后置通知
System.out.println(method.getName() + "方法执行完成");
return result;
}
}
// 生成代理对象
public class ProxyTest {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
UserService proxy = (UserService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogInvocationHandler(target)
);
proxy.createUser(new User()); // 调用代理对象的方法
}
}
特点
- 基于接口实现,目标对象必须实现至少一个接口
- 生成的代理类实现了目标对象的所有接口
- 只能代理接口中定义的方法
2. CGLIB 动态代理
CGLIB(Code Generation Library)是一个第三方代码生成库,它基于继承实现动态代理。
实现原理
- 编写目标对象,不需要实现接口
- 编写一个方法拦截器实现
MethodInterceptor接口,在intercept方法中编写切面逻辑 - 调用
Enhancer.create()方法生成代理对象,代理对象是目标对象的子类
代码示例
// 目标对象,不需要实现接口
public class UserService {
public void createUser(User user) {
System.out.println("创建用户:" + user);
}
}
// 方法拦截器
public class LogMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 前置通知
System.out.println("开始执行" + method.getName() + "方法");
// 执行目标方法
Object result = proxy.invokeSuper(obj, args);
// 后置通知
System.out.println(method.getName() + "方法执行完成");
return result;
}
}
// 生成代理对象
public class CglibTest {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserService.class);
enhancer.setCallback(new LogMethodInterceptor());
UserService proxy = (UserService) enhancer.create();
proxy.createUser(new User()); // 调用代理对象的方法
}
}
特点
- 基于继承实现,目标对象不需要实现接口
- 生成的代理类是目标对象的子类
- 可以代理目标对象的所有非 final 方法
3. JDK 动态代理 vs CGLIB 动态代理
这是面试最常考的问题之一,我整理了一张详细的对比表:
| 特性 | JDK 动态代理 | CGLIB 动态代理 |
|---|---|---|
| 实现方式 | 基于接口 | 基于继承 |
| 目标对象要求 | 必须实现至少一个接口 | 不需要实现接口,不能是 final 类 |
| 可代理方法 | 只能代理接口中定义的方法 | 可以代理所有非 final 方法 |
| 生成代理的速度 | 快 | 慢(需要生成字节码) |
| 执行速度 | 慢(反射调用) | 快(直接调用父类方法) |
| Spring 默认版本 | Spring 3.x 及以前 | Spring 4.x 及以后(Spring Boot 2.x 默认) |
4. Spring 如何选择动态代理?
Spring 会根据目标对象是否实现接口来自动选择动态代理方式:
- 如果目标对象实现了接口,默认使用 JDK 动态代理
- 如果目标对象没有实现接口,默认使用 CGLIB 动态代理
- 可以通过配置强制使用 CGLIB 动态代理:
spring: aop: proxy-target-class: true # 强制使用CGLIB动态代理
四、Spring AOP 完整执行流程
理解了动态代理的原理,我们来看一下 Spring AOP 的完整执行流程,从容器启动到方法调用的全过程:
阶段 1:容器启动
- Spring 容器启动,扫描所有的 Bean
- 扫描所有带有
@Aspect注解的类,将其识别为切面 - 解析每个切面中的切点表达式和通知
- 为每个匹配切点的 Bean 生成代理对象
- 将代理对象注册到 Spring 容器中,替代原始的目标对象
阶段 2:方法调用
- 客户端调用 Bean 的方法,实际上调用的是代理对象的方法
- 代理对象的方法被调用时,会触发拦截器链
- 按照顺序执行所有匹配的前置通知
- 执行目标对象的方法
- 按照顺序执行所有匹配的返回通知(如果正常返回)
- 按照顺序执行所有匹配的异常通知(如果抛出异常)
- 按照顺序执行所有匹配的后置通知
- 返回方法执行结果
执行流程图

五、@AspectJ 注解实战
Spring AOP 支持使用 @AspectJ 注解来定义切面,这是目前最常用也是最简洁的方式。
1. 引入依赖
首先需要在 pom.xml 中引入 Spring AOP 的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 定义切面
一个完整的切面定义包含三个部分:切面类、切点、通知。
@Aspect // 标记这是一个切面
@Component // 将切面交给Spring管理
public class LogAspect {
// 定义切点,匹配com.example.service包下的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void servicePointcut() {}
// 前置通知
@Before("servicePointcut()")
public void before(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("【前置通知】执行" + methodName + "方法,参数:" + Arrays.toString(args));
}
// 后置通知
@After("servicePointcut()")
public void after(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【后置通知】" + methodName + "方法执行完成");
}
// 返回通知
@AfterReturning(pointcut = "servicePointcut()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【返回通知】" + methodName + "方法返回结果:" + result);
}
// 异常通知
@AfterThrowing(pointcut = "servicePointcut()", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, Exception e) {
String methodName = joinPoint.getSignature().getName();
System.out.println("【异常通知】" + methodName + "方法抛出异常:" + e.getMessage());
}
// 环绕通知
@Around("servicePointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String methodName = joinPoint.getSignature().getName();
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // 执行原方法
long end = System.currentTimeMillis();
System.out.println("【环绕通知】" + methodName + "方法执行耗时:" + (end - start) + "ms");
return result;
} catch (Throwable e) {
System.out.println("【环绕通知】" + methodName + "方法执行异常");
throw e;
}
}
}
3. 切点表达式详解
切点表达式是 AOP 中最灵活也最容易出错的部分,最常用的是execution表达式,语法如下:
execution(修饰符? 返回类型 包名.类名.方法名(参数) 异常?)
?表示可选部分*表示任意字符..表示任意参数或任意子包
常用示例:
// 匹配所有public方法
@Pointcut("execution(public * *(..))")
// 匹配com.example.service包下的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
// 匹配com.example.service包及其子包下的所有方法
@Pointcut("execution(* com.example.service..*.*(..))")
// 匹配所有以create开头的方法
@Pointcut("execution(* create*(..))")
// 匹配UserService类的所有方法
@Pointcut("execution(* com.example.service.UserService.*(..))")
六、切面执行顺序
在实际项目中,我们可能会定义多个切面,比如日志切面、权限切面、事务切面。这时候就需要控制这些切面的执行顺序。
1. 同一个切面内的通知顺序
同一个切面内的不同通知按照以下顺序执行:
- 环绕通知的前置部分
- 前置通知(@Before)
- 目标方法执行
- 环绕通知的后置部分
- 后置通知(@After)
- 返回通知(@AfterReturning)/ 异常通知(@AfterThrowing)
2. 多个切面之间的执行顺序
多个切面之间的执行顺序可以通过@Order注解或Ordered接口来控制:
@Order注解的值越小,优先级越高,越先执行- 如果没有指定
@Order,默认优先级最低
示例:
@Aspect
@Component
@Order(1) // 优先级最高,最先执行
public class AuthAspect {
// 权限校验逻辑
}
@Aspect
@Component
@Order(2) // 优先级次之
public class LogAspect {
// 日志记录逻辑
}
@Aspect
@Component
@Order(3) // 优先级最低,最后执行
public class TransactionAspect {
// 事务管理逻辑
}
执行顺序:
AuthAspect前置通知 → LogAspect前置通知 → TransactionAspect前置通知
↓
执行目标方法
↓
TransactionAspect后置通知 → LogAspect后置通知 → AuthAspect后置通知
可以看到,切面的执行顺序是先进后出的,优先级高的切面先执行前置通知,后执行后置通知。
七、Spring AOP vs AspectJ
很多人搞不清 Spring AOP 和 AspectJ 的区别,以为它们是同一个东西。其实它们是完全不同的两个框架,只是 Spring AOP 借鉴了 AspectJ 的注解风格。
| 特性 | Spring AOP | AspectJ |
|---|---|---|
| 实现方式 | 基于动态代理的运行时织入 | 基于字节码的编译时 / 类加载时织入 |
| 织入时机 | 运行时 | 编译时、类加载时、运行时 |
| 功能 | 有限,只能代理 Spring Bean 的方法 | 强大,可以代理所有对象(包括非 Spring 管理的对象) |
| 复杂度 | 低,简单易用 | 高,需要特殊的编译器和配置 |
| 性能 | 一般,动态代理有一定开销 | 高,直接修改字节码,几乎没有性能开销 |
| 适用场景 | 大多数企业级应用,简单的 AOP 需求 | 复杂的 AOP 需求,需要代理非 Spring 管理的对象 |
核心结论:Spring AOP 是为了解决企业级应用中最常见的 AOP 需求而设计的,它简单易用,能够满足 90% 以上的场景。如果需要更强大的 AOP 功能,可以使用 AspectJ。
八、面试重点:AOP 不生效的 8 种常见场景
AOP 不生效是面试中最常考的问题,也是实际项目中最容易踩的坑。我整理了 8 种最常见的场景,每个场景都包含错误示例、原因分析和解决方案。
场景 1:方法不是 public 的
错误示例:
@Service
public class UserService {
// private方法,AOP不生效
@Transactional
private void createUser(User user) {
userMapper.insert(user);
}
}
原因:Spring AOP 基于动态代理实现,JDK 动态代理只能代理接口中的 public 方法,CGLIB 动态代理只能代理 public 和 protected 方法。private 方法无法被代理。
解决方案:将方法改为 public。
场景 2:同一个类中方法调用(最常见)
错误示例:
@Service
public class UserService {
public void createUser(User user) {
// 调用本类的另一个方法,AOP不生效
this.logUser(user);
userMapper.insert(user);
}
@Transactional
public void logUser(User user) {
logMapper.insert(user);
}
}
原因:当在同一个类中调用方法时,调用的是原始对象的方法,而不是代理对象的方法,所以 AOP 不会生效。
解决方案:有三种解决方案,推荐使用第一种或第三种。
方案 1:注入自己的代理对象
@Service
public class UserService {
@Autowired
private UserService userService; // 注入自己的代理对象
public void createUser(User user) {
userService.logUser(user); // 调用代理对象的方法
userMapper.insert(user);
}
@Transactional
public void logUser(User user) {
logMapper.insert(user);
}
}
方案 2:使用 AopContext.currentProxy ()
@Service
public class UserService {
public void createUser(User user) {
// 获取当前代理对象
UserService proxy = (UserService) AopContext.currentProxy();
proxy.logUser(user); // 调用代理对象的方法
userMapper.insert(user);
}
@Transactional
public void logUser(User user) {
logMapper.insert(user);
}
}
注意:需要在启动类上添加@EnableAspectJAutoProxy(exposeProxy = true)来暴露代理对象。
方案 3:重构代码,将方法放到另一个类中
@Service
public class LogService {
@Transactional
public void logUser(User user) {
logMapper.insert(user);
}
}
@Service
public class UserService {
@Autowired
private LogService logService;
public void createUser(User user) {
logService.logUser(user); // 调用其他类的方法
userMapper.insert(user);
}
}
场景 3:类没有被 Spring 管理
错误示例:
// 没有加@Service注解,没有被Spring管理
public class UserService {
@Transactional
public void createUser(User user) {
userMapper.insert(user);
}
}
原因:Spring AOP 只能代理 Spring 容器中的 Bean。如果类没有被 Spring 管理,就不会生成代理对象,AOP 自然不会生效。
解决方案:给类添加@Service、@Component等注解,让 Spring 管理这个类。
场景 4:方法是 static 或 final 的
错误示例:
@Service
public class UserService {
// static方法,AOP不生效
@Transactional
public static void createUser(User user) {
userMapper.insert(user);
}
// final方法,AOP不生效
@Transactional
public final void updateUser(User user) {
userMapper.update(user);
}
}
原因:
- static 方法属于类,不属于对象,无法被代理
- final 方法不能被重写,CGLIB 无法生成子类代理
解决方案:去掉 static 或 final 修饰符。
场景 5:异常被捕获没有抛出
错误示例:
@Service
public class UserService {
@Transactional
public void createUser(User user) {
try {
userMapper.insert(user);
int i = 1 / 0; // 抛出异常
} catch (Exception e) {
// 手动捕获异常,没有抛出,事务不会回滚
e.printStackTrace();
}
}
}
原因:Spring AOP 的异常通知和事务回滚只有在异常没有被捕获并抛出时才会触发。如果手动捕获了异常但没有重新抛出,AOP 不会感知到异常。
解决方案:捕获异常后重新抛出,或者手动设置事务回滚。
场景 6:错误的切点表达式
错误示例:
// 切点表达式写错了,匹配不到任何方法
@Pointcut("execution(* com.example.service.UserService.create(..))")
public void pointcut() {}
原因:切点表达式写错了,没有匹配到任何方法,所以 AOP 不会生效。
解决方案:仔细检查切点表达式,确保它能正确匹配目标方法。
场景 7:切面没有被 Spring 管理
错误示例:
// 没有加@Component注解,切面没有被Spring管理
@Aspect
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void before() {
System.out.println("前置通知");
}
}
原因:切面类本身也需要被 Spring 管理,否则 Spring 无法识别它是一个切面。
解决方案:给切面类添加@Component注解。
场景 8:使用了 @Async 或 @Transactional 注解导致代理冲突
错误示例:
@Service
public class UserService {
@Async
@Transactional
public void createUser(User user) {
userMapper.insert(user);
}
}
原因:@Async和@Transactional都会生成代理对象,如果多个注解同时使用,可能会导致代理冲突,AOP 不生效。
解决方案:将不同的注解放到不同的类中,或者调整代理的顺序。
九、AOP 的性能影响
很多人担心 AOP 会影响性能,其实这个担心是多余的。
性能测试数据
根据 Spring 官方的性能测试数据:
- JDK 动态代理的性能开销大约是每次调用 1-10 微秒
- CGLIB 动态代理的性能开销大约是每次调用 0.5-5 微秒
也就是说,即使每秒调用 10 万次 AOP 方法,总的性能开销也只有 0.05-1 秒,对于绝大多数应用来说完全可以忽略不计。
影响性能的因素
AOP 的性能影响主要来自以下几个方面:
- 通知类型:环绕通知的性能开销最大,因为它需要调用
joinPoint.proceed() - 通知数量:同一个方法上的通知越多,性能开销越大
- 通知逻辑复杂度:通知中的逻辑越复杂,性能开销越大
最佳实践
- 尽量使用简单的通知类型,避免不必要的环绕通知
- 不要在通知中编写复杂的业务逻辑
- 对于性能要求极高的场景,可以考虑使用 AspectJ 的编译时织入,几乎没有性能开销
十、最佳实践与避坑指南
- 优先使用 @AspectJ 注解风格:比 XML 配置更简洁,更易于维护
- 合理使用切点表达式:尽量精确匹配目标方法,避免匹配过多不必要的方法
- 避免在通知中抛出异常:除非必要,否则不要在通知中抛出异常,以免影响原方法的执行
- 环绕通知必须调用 proceed ():如果忘记调用,会导致原方法不执行
- 同一个类中方法调用使用代理对象:避免 AOP 不生效
- 不要过度使用 AOP:AOP 虽然强大,但不要滥用,否则会导致代码难以理解和调试
- 控制切面的执行顺序:使用
@Order注解明确指定多个切面的执行顺序
十一、高频面试题解答
-
问:AOP 的核心概念是什么? 答:AOP 的核心概念包括切面、连接点、切点、通知、目标对象、代理对象和织入。切面是横切关注点的模块化,连接点是程序执行的某个点,切点是匹配连接点的表达式,通知是切面在连接点上执行的动作。
-
问:JDK 动态代理和 CGLIB 动态代理有什么区别? 答:JDK 动态代理基于接口实现,目标对象必须实现接口,只能代理接口中的方法;CGLIB 动态代理基于继承实现,目标对象不需要实现接口,可以代理所有非 final 方法。Spring 4.x 以后默认使用 CGLIB 动态代理。
-
问:为什么同一个类中方法调用 AOP 不生效?怎么解决? 答:因为同一个类中方法调用时,调用的是原始对象的方法,而不是代理对象的方法。解决方案有三种:注入自己的代理对象、使用 AopContext.currentProxy () 获取代理对象、将方法重构到另一个类中。
-
问:AOP 不生效的常见场景有哪些? 答:常见的场景有:方法不是 public 的、同一个类中方法调用、类没有被 Spring 管理、方法是 static 或 final 的、异常被捕获没有抛出、错误的切点表达式、切面没有被 Spring 管理、代理冲突。
-
问:如何控制多个切面的执行顺序? 答:可以使用
@Order注解或Ordered接口来控制切面的执行顺序。@Order的值越小,优先级越高,越先执行。 -
问:Spring AOP 和 AspectJ 有什么区别? 答:Spring AOP 基于动态代理的运行时织入,简单易用,只能代理 Spring Bean 的方法;AspectJ 基于字节码的编译时 / 类加载时织入,功能强大,可以代理所有对象,但复杂度更高。
-
问:AOP 会影响性能吗? 答:AOP 有一定的性能开销,但对于绝大多数应用来说完全可以忽略不计。性能影响主要来自通知的数量和逻辑复杂度,合理使用 AOP 不会对性能造成明显影响。
十二、总结
Spring AOP 是 Spring 框架最优雅的特性之一,它通过横向抽取重复代码,实现了业务逻辑与非业务逻辑的解耦,大大提高了代码的可维护性和复用性。
回顾一下全文的核心内容:
- AOP 的核心是将横切关注点从业务逻辑中抽取出来,封装成切面
- Spring AOP 基于动态代理实现,支持 JDK 动态代理和 CGLIB 动态代理
- 同一个类中方法调用 AOP 不生效是最常见的坑,需要使用代理对象调用
- AOP 不生效有 8 种常见场景,掌握这些场景可以快速定位和解决问题
- 合理使用 AOP 不会对性能造成明显影响
理解了 Spring AOP 的原理和坑点,你就能在实际项目中正确使用 AOP,写出更优雅、更健壮的代码。同时,这些内容也是面试中的高频考点,掌握了它们,你就能轻松应对所有 AOP 相关的面试题。
更多推荐



所有评论(0)