Skip to content

Spring AOP 原理与代理机制深度解析

面试官:说说 Spring AOP 的实现原理?

:Spring AOP 通过动态代理实现,支持 JDK 动态代理和 CGLIB 两种方式。JDK 基于接口,通过反射调用;CGLIB 基于继承,通过字节码生成。Spring Boot 2.0+ 默认使用 CGLIB。

面试官:那 CGLIB 比 JDK 动态代理快吗?

很多人回答”CGIB 更快”,但这是一个陷阱。创建速度 JDK 更快,调用速度 CGLIB 更快,且 JDK 8 之后差距已大幅缩小。


链式追问一:JDK 动态代理 vs CGLIB

Section titled “链式追问一:JDK 动态代理 vs CGLIB”

Q1:JDK 动态代理的实现原理?必考

Section titled “Q1:JDK 动态代理的实现原理?”

回答要点

JDK 动态代理原理:
1. 代理类实现目标类的相同接口
2. 通过 Proxy.newProxyInstance() 在运行时生成代理类字节码
3. 代理类持有 InvocationHandler,方法调用转发给 handler
4. InvocationHandler.invoke() 通过反射调用目标方法
核心限制:目标类必须实现至少一个接口

代码示例

// 目标接口
public interface UserService {
void createUser(String name);
User getUser(Long id);
}
// 目标实现
public class UserServiceImpl implements UserService {
@Override
public void createUser(String name) {
System.out.println("创建用户: " + name);
}
@Override
public User getUser(Long id) {
return new User(id, "张三");
}
}
// JDK 动态代理
public class JdkProxyDemo {
public static void main(String[] args) {
UserService target = new UserServiceImpl();
// 创建代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
UserServiceImpl.class.getClassLoader(), // 类加载器
new Class[]{UserService.class}, // 代理的接口
new InvocationHandler() { // 调用处理器
@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("[后置] 返回: " + result);
return result;
}
}
);
proxy.createUser("李四");
}
}

运行时生成的代理类(反编译)

// JVM 运行时生成:com.sun.proxy.$Proxy0
public final class $Proxy0 extends Proxy implements UserService {
private static Method m1; // createUser 方法
private static Method m2; // getUser 方法
static {
m1 = UserService.class.getMethod("createUser", String.class);
m2 = UserService.class.getMethod("getUser", Long.class);
}
public $Proxy0(InvocationHandler h) {
super(h); // 保存到 Proxy.h 字段
}
@Override
public void createUser(String name) {
try {
// 调用 InvocationHandler.invoke()
super.h.invoke(this, m1, new Object[]{name});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public User getUser(Long id) {
try {
return (User) super.h.invoke(this, m2, new Object[]{id});
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

本质一句话:JDK 动态代理在运行时生成实现相同接口的代理类,方法调用通过 InvocationHandler 转发,最终通过反射调用目标方法。


Q2:CGLIB 的实现原理?必考

Section titled “Q2:CGLIB 的实现原理?”

回答要点

CGLIB(Code Generation Library)原理:
1. 通过 ASM 字节码库生成目标类的子类
2. 子类重写所有非 final 方法
3. 方法调用转发给 MethodInterceptor.intercept()
4. 使用 FastClass(字节码生成的方法调用器)调用目标方法
→ 比反射更快(直接生成调用字节码)
核心限制:无法代理 final 类和 final 方法

代码示例

// 目标类(无需接口)
public class OrderService {
public void createOrder(String orderId) {
System.out.println("创建订单: " + orderId);
}
public final void cancelOrder(String orderId) {
System.out.println("取消订单: " + orderId); // final 方法,无法代理
}
}
// CGLIB 代理
public class CglibProxyDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OrderService.class); // 设置父类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("[前置] 方法: " + method.getName());
// 方式1:反射调用(慢)
// Object result = method.invoke(target, args);
// 方式2:FastClass 调用(快)
Object result = proxy.invokeSuper(obj, args);
System.out.println("[后置] 返回: " + result);
return result;
}
});
OrderService proxy = (OrderService) enhancer.create();
proxy.createOrder("ORDER-001");
}
}

生成的代理类(简化)

// CGLIB 生成:OrderService$$EnhancerBySpringCGLIB$$12345678
public class OrderService$$EnhancerBySpringCGLIB extends OrderService {
private MethodInterceptor interceptor;
// FastClass 索引(方法编号)
private static final int CREATE_ORDER = 0;
private static final int CANCEL_ORDER = 1;
static {
// ASM 生成的 FastClass,直接通过索引调用方法(比反射快)
FastClass fastClass = new OrderServiceFastClass();
}
@Override
public void createOrder(String orderId) {
// 转发给 MethodInterceptor
interceptor.intercept(
this,
createOrderMethod,
new Object[]{orderId},
createOrderProxy // MethodProxy(FastClass 封装)
);
}
// final 方法无法重写,不会被代理
// public final void cancelOrder(String orderId) { ... }
}

Q3:JDK 动态代理和 CGLIB 的性能对比?高频

Section titled “Q3:JDK 动态代理和 CGLIB 的性能对比?”

性能对比表格

对比维度JDK 动态代理CGLIB说明
实现机制实现接口 + 反射调用继承子类 + FastClassCGLIB 无需接口
创建速度★★★★★★★★☆☆JDK 内置,CGLIB 需生成字节码
调用速度★★☆☆☆★★★★★FastClass 比反射快
内存占用大(生成多个辅助类)CGLIB 生成代理类 + FastClass
JDK 8+ 差距JDK 优化了反射性能

性能测试数据(JDK 17)

创建 10000 个代理对象:
JDK 动态代理: ~50ms
CGLIB: ~200ms
调用代理方法 100万次:
JDK 动态代理: ~800ms(反射调用)
CGLIB: ~300ms(FastClass 调用)
JDK 8 之前:
CGLIB 调用速度快 5-10 倍
JDK 8+:
差距缩小至 2-3 倍(JDK 优化反射 + InvokeDynamic)

Spring 选择代理方式的逻辑

proxyTargetClass = false(Spring 4.x 默认):
目标类有接口 → JDK 动态代理
目标类无接口 → CGLIB
proxyTargetClass = true(Spring Boot 2.0+ 默认):
始终使用 CGLIB(除非目标本身就是接口类型)
Spring Boot 2.0 改为默认 CGLIB 的原因:
1. 避免代理类型不一致(同一 Bean 有时 JDK 有时 CGLIB)
2. 避免接口未实现所有方法导致的代理失败
3. 更符合实际使用场景(多数 Bean 无接口)

Q1:AOP 的核心概念有哪些?必考

Section titled “Q1:AOP 的核心概念有哪些?”

核心术语

┌─────────────────────────────────────────────────────────────┐
│ Aspect(切面) │
├─────────────────────────────────────────────────────────────┤
│ 封装横切关注点的模块,包含切点 + 通知 │
│ 例:事务切面、日志切面、权限切面 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Join Point(连接点) │
├─────────────────────────────────────────────────────────────┤
│ 程序执行过程中可以被拦截的点 │
│ Spring AOP 只支持方法执行连接点 │
│ AspectJ 还支持字段访问、构造方法、静态方法等 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Pointcut(切点) │
├─────────────────────────────────────────────────────────────┤
│ 描述在哪些连接点上应用通知的表达式 │
│ 例:execution(* com.example.service.*.*(..)) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Advice(通知) │
├─────────────────────────────────────────────────────────────┤
│ 在连接点处执行的具体逻辑 │
│ • @Before:方法执行前 │
│ • @After:方法执行后(无论是否异常) │
│ • @AfterReturning:正常返回后 │
│ • @AfterThrowing:抛出异常后 │
│ • @Around:前后都可控制,最强大 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Target(目标对象) │
├─────────────────────────────────────────────────────────────┤
│ 被代理的原始对象(业务逻辑所在) │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Proxy(代理对象) │
├─────────────────────────────────────────────────────────────┤
│ AOP 框架创建的代理,调用者实际持有的是代理对象 │
│ 代理对象 = 目标对象 + 通知逻辑 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Weaving(织入) │
├─────────────────────────────────────────────────────────────┤
│ 将切面应用到目标对象,创建代理对象的过程 │
│ • Spring AOP:运行时织入(动态代理) │
│ • AspectJ:编译时/类加载时织入(字节码修改) │
└─────────────────────────────────────────────────────────────┘

代码示例

@Aspect
@Component
public class LoggingAspect {
// 切点:匹配 service 包下所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 前置通知
@Before("serviceLayer()")
public void before(JoinPoint joinPoint) {
System.out.println("[前置] 方法: " + joinPoint.getSignature().getName());
}
// 环绕通知(最强大,可控制方法是否执行)
@Around("serviceLayer()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
System.out.println("[环绕前置] 方法: " + joinPoint.getSignature().getName());
try {
// 执行目标方法
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
System.out.println("[环绕后置] 耗时: " + elapsed + "ms");
return result;
} catch (Exception e) {
System.out.println("[环绕异常] 异常: " + e.getMessage());
throw e;
}
}
// 后置通知(无论是否异常都执行)
@After("serviceLayer()")
public void after(JoinPoint joinPoint) {
System.out.println("[后置] 方法执行完成: " + joinPoint.getSignature().getName());
}
// 返回通知(正常返回后执行)
@AfterReturning(pointcut = "serviceLayer()", returning = "result")
public void afterReturning(JoinPoint joinPoint, Object result) {
System.out.println("[返回] 结果: " + result);
}
// 异常通知(抛出异常后执行)
@AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("[异常] 异常: " + ex.getMessage());
}
}

Q2:多个通知的执行顺序是怎样的?高频

Section titled “Q2:多个通知的执行顺序是怎样的?”

同一切面的执行顺序

方法正常执行:
@Around(前)→ @Before → 目标方法 → @Around(后)→ @AfterReturning → @After
方法抛出异常:
@Around(前)→ @Before → 目标方法(异常)→ @AfterThrowing → @After
关键理解:
1. @Around 是最外层,包裹整个过程
2. @After 总是最后执行(finally 语义)
3. @AfterReturning 和 @AfterThrowing 互斥

多个切面的执行顺序(洋葱圈模型)

@Order(1) → LogAspect
@Order(2) → TransactionAspect
@Order(3) → ValidationAspect
执行顺序:
Order(1) @Around前
Order(2) @Around前
Order(3) @Around前
Order(1) @Before
Order(2) @Before
Order(3) @Before
目标方法
Order(3) @AfterReturning
Order(2) @AfterReturning
Order(1) @AfterReturning
Order(3) @Around后
Order(2) @Around后
Order(1) @Around后
类比:洋葱圈,从外到内执行前置,从内到外执行后置

代码示例

@Aspect
@Component
@Order(1)
public class LogAspect {
@Around("execution(* com.example..*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("LogAspect @Around 前");
Object result = joinPoint.proceed();
System.out.println("LogAspect @Around 后");
return result;
}
@Before("execution(* com.example..*(..))")
public void before() {
System.out.println("LogAspect @Before");
}
}
@Aspect
@Component
@Order(2)
public class TransactionAspect {
@Around("execution(* com.example..*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("TransactionAspect @Around 前");
Object result = joinPoint.proceed();
System.out.println("TransactionAspect @Around 后");
return result;
}
@Before("execution(* com.example..*(..))")
public void before() {
System.out.println("TransactionAspect @Before");
}
}
// 执行结果:
// LogAspect @Around 前
// TransactionAspect @Around 前
// LogAspect @Before
// TransactionAspect @Before
// 目标方法
// TransactionAspect @After
// LogAspect @After
// TransactionAspect @Around 后
// LogAspect @Around 后

Q1:为什么 Spring AOP 自调用会失效?必考

Section titled “Q1:为什么 Spring AOP 自调用会失效?”

场景示例

@Service
public class OrderService {
@Transactional
public void createOrder() {
System.out.println("创建订单");
this.sendNotification(); // 自调用!@Transactional 失效
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification() {
System.out.println("发送通知");
// 期望:开启新事务
// 实际:@Transactional 不生效,与 createOrder 共用同一事务
}
}

失效原理图解

调用者持有的对象:
┌─────────────────────────────────────┐
│ OrderService 代理对象 │
├─────────────────────────────────────┤
│ createOrder() { │
│ 1. 开启事务 │
│ 2. target.createOrder() ←─────┐│
│ 3. 提交事务 调用真实││
│ } 对象的 ││
│ 方法││
│ sendNotification() { ││
│ 1. 开启新事务 ││
│ 2. target.sendNotification() ││
│ 3. 提交事务 ││
│ } ││
└─────────────────────────────────────┘│
┌─────────────────────────────────────┐│
│ OrderService 真实对象 ││
├─────────────────────────────────────┤│
│ createOrder() { ││
│ System.out.println("创建订单"); ││
│ this.sendNotification(); ───────┘│ ← this 是真实对象,不是代理!
│ } │ 绕过了代理,AOP 失效
│ │
│ sendNotification() { │
│ System.out.println("发送通知"); │
│ // 没有事务逻辑 │
│ } │
└─────────────────────────────────────┘

根本原因

Spring AOP 通过代理实现:
调用者 → 代理对象 → 真实对象
自调用时:
在真实对象内部调用 this.method()
this = 真实对象(非代理对象)
→ 绕过了代理,AOP 通知不执行
→ @Transactional、@Async、@Cacheable 等注解全部失效

Q2:如何解决自调用失效问题?实战

Section titled “Q2:如何解决自调用失效问题?”

解决方案对比

方案实现方式优点缺点推荐度
重构代码提取到另一个 Bean解耦,最佳实践需要改代码★★★★★
注入自身@Autowired private OrderService self简单,无需改代码略显奇怪★★★☆☆
AopContextAopContext.currentProxy()Spring 原生支持需开启 exposeProxy★★☆☆☆
ApplicationContextcontext.getBean()通用方案侵入性强★★☆☆☆

代码示例

// 方案1:重构代码(最佳)
@Service
public class OrderService {
@Autowired
private NotificationService notificationService; // 提取到独立 Bean
@Transactional
public void createOrder() {
System.out.println("创建订单");
notificationService.sendNotification(); // 通过 Bean 间调用,触发代理
}
}
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification() {
System.out.println("发送通知"); // 生效!
}
}
// 方案2:注入自身
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入代理对象(Spring 4.3+ 支持)
@Transactional
public void createOrder() {
System.out.println("创建订单");
self.sendNotification(); // 通过代理调用,生效!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification() {
System.out.println("发送通知");
}
}
// 方案3:AopContext.currentProxy()
@EnableAspectJAutoProxy(exposeProxy = true) // 必须开启
@SpringBootApplication
public class Application {}
@Service
public class OrderService {
@Transactional
public void createOrder() {
System.out.println("创建订单");
OrderService proxy = (OrderService) AopContext.currentProxy();
proxy.sendNotification(); // 生效!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification() {
System.out.println("发送通知");
}
}
// 方案4:ApplicationContext
@Service
public class OrderService implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext context) {
this.context = context;
}
@Transactional
public void createOrder() {
System.out.println("创建订单");
OrderService proxy = context.getBean(OrderService.class);
proxy.sendNotification(); // 生效!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification() {
System.out.println("发送通知");
}
}

Q3:哪些情况下 AOP 会失效?高频

Section titled “Q3:哪些情况下 AOP 会失效?”

失效场景总结

1. 自调用(最常见)
同一个类内部方法调用,this 是真实对象,绕过代理
2. 方法不是 public
private、protected、package-private 方法无法被 CGLIB 重写
Spring AOP 只能代理 public 方法
3. final 类或 final 方法
CGLIB 无法继承 final 类,无法重写 final 方法
JDK 动态代理不受限(代理接口)
4. Bean 未被 Spring 管理
通过 new 创建的对象,而非从容器获取
没有代理包装,注解无效
5. 多线程调用
@Transactional 方法中启动新线程,新线程中的数据库操作不在事务中
Spring 事务通过 ThreadLocal 绑定连接
6. 异常被吞掉
@Transactional 默认只回滚 RuntimeException
catch 后未重新抛出异常,事务不会回滚

代码示例

// 失效场景1:方法不是 public
@Service
public class OrderService {
@Transactional
protected void createOrder() { // protected,失效!
// 数据库操作
}
}
// 失效场景2:final 方法
@Service
public class OrderService {
@Transactional
public final void createOrder() { // final,失效!
// 数据库操作
}
}
// 失效场景3:new 创建的对象
public class OrderController {
public void test() {
OrderService orderService = new OrderService(); // 未被 Spring 管理
orderService.createOrder(); // @Transactional 失效
}
}
// 失效场景4:多线程
@Service
public class OrderService {
@Transactional
public void createOrder() {
new Thread(() -> {
// 新线程中的操作不在事务中
userMapper.insert(user);
}).start();
}
}
// 失效场景5:异常被吞掉
@Service
public class OrderService {
@Transactional
public void createOrder() {
try {
orderMapper.insert(order);
throw new RuntimeException("出错了");
} catch (Exception e) {
log.error("异常", e);
// 没有重新抛出,事务提交(数据写入数据库)
}
}
}

Q1:常用的切点表达式有哪些?中频

Section titled “Q1:常用的切点表达式有哪些?”

execution 表达式语法

execution(修饰符? 返回类型 类路径? 方法名(参数) 异常?)
通配符:
* → 匹配任意字符(单个层级)
.. → 匹配任意字符(多个层级)
+ → 匹配指定类及其子类

常用切点表达式

@Aspect
@Component
public class CommonPointcuts {
// 1. 匹配 service 包下所有类的所有方法
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayer() {}
// 2. 匹配 service 包及子包下所有方法
@Pointcut("execution(* com.example.service..*(..))")
public void serviceLayerRecursive() {}
// 3. 匹配返回 String 的 get 开头方法
@Pointcut("execution(String com.example..*.get*(..))")
public void getterMethod() {}
// 4. 匹配特定类的特定方法
@Pointcut("execution(public * com.example.service.UserService.createUser(..))")
public void specificMethod() {}
// 5. 匹配有 @Transactional 注解的方法
@Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalMethod() {}
// 6. 匹配类型上有 @Service 注解的所有方法
@Pointcut("within(@org.springframework.stereotype.Service *)")
public void serviceClass() {}
// 7. 匹配参数类型为 String 的方法
@Pointcut("execution(* *(String))")
public void methodWithStringParam() {}
// 8. 匹配参数包含 HttpServletRequest 的方法
@Pointcut("args(javax.servlet.http.HttpServletRequest,..)")
public void methodWithHttpServletRequest() {}
// 9. 组合表达式:service 包但排除 internal 方法
@Pointcut("execution(* com.example.service.*.*(..)) " +
"&& !execution(* com.example.service.*.internal*(..))")
public void serviceExcludeInternal() {}
// 10. 匹配实现了特定接口的类的所有方法
@Pointcut("this(com.example.service.BaseService)")
public void implementsBaseService() {}
}

性能优化建议

切点表达式性能(从快到慢):
1. within() → 只检查类型,不检查方法
2. execution() → 检查方法签名
3. @annotation() → 反射读取注解
4. args() → 运行时检查参数类型
优化建议:
• 避免过度使用 @annotation(反射性能开销)
• 使用 within() 限制范围,缩小匹配范围
• 复杂表达式拆分为多个切点,用 && 连接
• 缓存切点计算结果(Spring 内部已缓存)

Q:JDK 动态代理和 CGLIB 的区别?

JDK 基于接口实现代理,通过反射调用;CGLIB 基于继承实现代理,通过 FastClass 调用。JDK 要求目标类有接口;CGLIB 无需接口但无法代理 final 类/方法。创建速度 JDK 更快;调用速度 CGLIB 更快(JDK 8+ 后差距缩小)。Spring Boot 2.0+ 默认 CGLIB。

Q:Spring AOP 为什么自调用会失效?

代理对象拦截外部调用,调用真实对象的方法。真实对象内部的 this.method() 调用的是真实对象本身(非代理),绕过了代理,AOP 通知不执行。解决方案:重构代码提取到另一个 Bean、注入自身、使用 AopContext.currentProxy()。

Q:多个通知的执行顺序?

同一切面:@Around(前)→ @Before → 目标方法 → @Around(后)→ @AfterReturning/AfterThrowing → @After。多个切面按 @Order 升序执行(洋葱圈模型),Order 小的先执行前置,后执行后置。

Q:@Transactional 加在 private 方法上有效吗?

无效。Spring AOP 通过 CGLIB 继承重写方法实现代理,private 方法无法被子类重写;JDK 动态代理也无法代理 private 方法。@Transactional 依赖 AOP 代理,private 方法上的注解不会生效。

Q:如何选择 JDK 动态代理还是 CGLIB?

Spring Boot 2.0+ 默认使用 CGLIB(proxyTargetClass=true)。功能上:无接口只能用 CGLIB;性能上:JDK 8+ 后差距不大;一致性:统一使用 CGLIB 避免代理类型不一致。实际开发中无需手动选择,交给 Spring 自动判断即可。