一文彻底搞懂 Spring 原始对象与代理对象(AOP核心原理)
在学习 Spring 的过程中,经常会听到几个概念:
- 原始对象(Target Object)
- 代理对象(Proxy Object)
- 动态代理
- AOP
- CGLIB / JDK Proxy
很多人知道:
Spring 的 AOP 是通过代理实现的
但很多人并不清楚:
- 什么是原始对象?
- 什么是代理对象?
- Spring 为什么要创建代理对象?
- 代理对象是如何生成的?
- 事务、日志、权限为什么必须使用代理?
本篇文章将 彻底讲清 Spring 中原始对象与代理对象的核心原理。
一、什么是原始对象(Target Object)
1 概念
原始对象(Target Object) 指的是:
开发者编写的真实业务对象。
例如:
java
@Service
public class UserService {
public void createUser() {
System.out.println("创建用户");
}
}
这个 UserService 对象就是 原始对象。
它只负责:
业务逻辑
例如:
下单
创建用户
查询数据
2 原始对象的问题
假设我们有如下需求:
调用 createUser 方法时
需要:
1 记录日志
2 开启事务
3 权限检查
4 统计耗时
如果直接写在业务代码里:
java
public void createUser(){
//日志
log.info("方法开始");
//权限
checkPermission();
//业务
System.out.println("创建用户");
}
问题:
业务代码被大量非业务逻辑污染
这违反了:
单一职责原则
因此 Spring 引入了:
AOP
而 AOP 的核心实现就是:
代理对象
二、什么是代理对象(Proxy Object)
1 概念
代理对象(Proxy Object) 指的是:
Spring 在运行时生成的一个对象,用来"代理"原始对象。
调用关系变成:
调用者
↓
代理对象
↓
原始对象
流程:
Controller
↓
Proxy(UserService)
↓
UserService
代理对象会在调用原始方法前后 添加额外逻辑。
例如:
日志
事务
权限
监控
2 代理对象执行流程
假设调用:
java
userService.createUser();
真实执行流程:
调用代理对象
↓
代理对象执行前置逻辑
↓
调用原始对象方法
↓
执行后置逻辑
伪代码:
java
public void createUser(){
//前置增强
log.info("方法开始");
//调用原始方法
target.createUser();
//后置增强
log.info("方法结束");
}
三、为什么 Spring 要使用代理?
Spring 需要解决的问题是:
在不修改业务代码的情况下
增加功能
例如:
事务
日志
权限
缓存
监控
如果不用代理:
每个方法都写事务代码
代码会变成:
java
try{
transaction.begin();
userService.createUser();
transaction.commit();
}catch(Exception e){
transaction.rollback();
}
非常复杂。
而使用代理:
开发者只需要写
@Transactional
Spring 自动完成事务逻辑。
四、Spring 中哪些功能依赖代理
Spring 中很多功能都是通过 代理对象实现的:
| 功能 | 原理 |
|---|---|
| AOP | 动态代理 |
| @Transactional | 事务代理 |
| @Cacheable | 缓存代理 |
| @Async | 异步代理 |
| @Retryable | 重试代理 |
例如:
java
@Transactional
public void createUser(){
}
Spring 会生成:
UserServiceProxy
五、Spring 代理的两种实现方式
Spring 代理有两种实现方式。
1 JDK 动态代理
JDK 自带代理机制。
要求:
必须有接口
示例:
java
public interface UserService {
void createUser();
}
实现类:
java
@Service
public class UserServiceImpl implements UserService {
public void createUser(){
System.out.println("创建用户");
}
}
Spring 生成:
UserServiceProxy
代理接口。
特点:
代理接口
2 CGLIB 代理
如果 没有接口,Spring 使用:
CGLIB
CGLIB 通过:
继承类
实现代理。
例如:
java
@Service
public class UserService {
public void createUser(){
System.out.println("创建用户");
}
}
Spring 会生成:
UserService$$EnhancerBySpring
本质:
继承 UserService
并重写方法。
六、Spring 如何选择代理方式
Spring 默认规则:
有接口 → JDK代理
无接口 → CGLIB代理
Spring Boot 2 之后:
默认 CGLIB
可以配置:
java
@EnableAspectJAutoProxy(proxyTargetClass = true)
七、Spring AOP 执行流程
完整调用链:
Controller
↓
Proxy
↓
Advice(前置通知)
↓
目标方法
↓
Advice(后置通知)
AOP 术语:
| 名称 | 说明 |
|---|---|
| Target | 原始对象 |
| Proxy | 代理对象 |
| Advice | 增强逻辑 |
| Aspect | 切面 |
| JoinPoint | 连接点 |
八、如何判断自己拿到的是代理对象
可以打印类名:
java
System.out.println(userService.getClass());
输出:
class com.demo.UserService$$EnhancerBySpringCGLIB
说明:
这是代理对象
九、Spring 代理常见坑(非常重要)
1 同类方法调用失效(事务失效)
例如:
java
@Service
public class UserService {
public void methodA(){
methodB();
}
@Transactional
public void methodB(){
}
}
问题:
methodB 的事务不会生效
原因:
没有经过代理对象
调用流程:
this.methodB()
而不是:
proxy.methodB()
2 private 方法不能代理
例如:
java
@Transactional
private void test(){
}
事务不会生效。
原因:
CGLIB 无法重写 private 方法
3 final 方法无法代理
因为:
CGLIB 通过继承实现代理
而:
final 方法不能被重写
十、原始对象 vs 代理对象总结
| 对比 | 原始对象 | 代理对象 |
|---|---|---|
| 创建者 | 开发者 | Spring |
| 作用 | 业务逻辑 | 增强逻辑 |
| 是否增强 | 否 | 是 |
| 是否拦截方法 | 否 | 是 |
| AOP使用 | 不直接使用 | 实际使用 |
调用关系:
Controller
↓
代理对象
↓
原始对象
十一、面试高频问题
1 什么是代理对象?
答:
代理对象是 Spring 在运行时生成的对象,
用于在调用目标方法前后添加增强逻辑。
2 Spring AOP 为什么使用代理?
答:
为了在不修改业务代码的情况下
增加功能(事务、日志、权限等)。
3 Spring 代理方式有哪些?
JDK动态代理
CGLIB代理
4 JDK 和 CGLIB 区别?
| JDK | CGLIB | |
|---|---|---|
| 代理方式 | 接口 | 继承 |
| 是否需要接口 | 是 | 否 |
| 性能 | 稍慢 | 稍快 |
5 为什么事务会失效?
原因通常是:
没有经过代理对象调用
例如:
同类方法调用
private方法
final方法
十二、一句话理解代理对象
Spring 的核心思想:
你写业务对象
Spring 给你生成代理对象
真正调用的是:
代理对象
而不是:
原始对象