⚛️前言
Spring容器管理Bean的方式以及代理机制可能会让问题变得复杂,但是需要明确一点:
-
Spring容器中默认存储的是代理对象
- 当某个Bean被AOP增强时(如通过
@Transactional
,@Cacheable
等),Spring容器实际创建并管理的是代理对象,而不是原始对象。
- 当某个Bean被AOP增强时(如通过
-
原始对象在哪?
- 代理对象内部持有一个对原始对象(也叫目标对象)的引用。这个原始对象由代理对象内部维护,但并不直接暴露在Spring应用上下文中。 在 Spring 框架中,这个问题涉及到 Bean 的生命周期管理和 AOP 代理机制的核心设计。以下是详细解释:
🛄图示内存结构:
java
Spring 应用上下文 (ApplicationContext)
│
├─── Bean名称: "payService"
│ 类型: PayService$$EnhancerBySpringCGLIB$$... [代理对象]
│ 内部结构:
│ ┌─────────────────────┐
│ │ CGLIB代理实例 │
│ │ ┌────────────────┐ │
│ │ │ TargetSource ────> 原始PayService对象 [在代理内部]
│ │ └────────────────┘ │
│ └─────────────────────┘
│
└─── 其他Bean...
🌱 Spring 容器中对象的存储机制
对象类型 | 存储位置 | 生命周期管理 | 可见性 |
---|---|---|---|
代理对象 | Spring 应用上下文(ApplicationContext) | 由 Spring 容器完全管理 | 唯一对外暴露的 Bean |
原始对象 | 代理对象内部 (作为 TargetSource) | 由代理对象管理 | 对开发者不可见 |
🔍 核心设计图解
graph TD
subgraph Spring ApplicationContext
A[Bean: payServiceProxy] --> B[TargetSource]
B --> C[原始对象实例]
end
-
容器启动阶段:
- Spring 创建原始对象(目标对象)
- 根据 AOP 配置创建代理对象(JDK 动态代理或 CGLIB 代理)
- 只将代理对象注册为 Spring Bean
- 原始对象作为代理的内部状态被封装
-
依赖注入时:
java@Autowired private PayService payService; // 这里获取的实际是代理对象
-
获取 Bean 时:
arduinocontext.getBean(PayService.class); // 返回代理对象
💡 为什么这样设计?
-
单一职责原则:
- Spring 容器只管理"业务逻辑入口"(代理对象)
- 原始对象作为实现细节被封装
-
AOP 透明性保障:
scss// 使用者无需关心是否被代理 payService.processPayment();
-
资源优化:
- 避免重复创建(代理对象复用原始对象)
- 减少 Spring 容器管理对象数量
🛠 如何验证容器中的对象?
java
// 1. 检查 Spring 上下文中的 Bean
PayService proxy = context.getBean(PayService.class);
System.out.println("Spring Bean 类型: " + proxy.getClass().getName());
// 输出: com.example.PayService$$EnhancerBySpringCGLIB$$...
// 2. 通过代理获取原始对象
if (proxy instanceof Advised) {
Object target = ((Advised) proxy).getTargetSource().getTarget();
System.out.println("原始对象类型: " + target.getClass().getName());
// 输出: com.example.PayService
System.out.println("是否相同对象: " + (proxy == target)); // 总是 false
}
⚠ 重要注意事项
-
原始对象的访问限制:
java// 错误尝试:这样获取的仍然是代理对象 @Autowired private PayService rawService; // 即使你认为是"原始对象"
-
原型作用域(prototype)的特殊性:
kotlin@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) public class PayService { ... }
- 每次请求时:创建新原始对象 → 创建新代理对象
- 但仍只暴露代理对象
-
代理对象标识特点:
比较方法 结果 原因说明 proxy == raw
false 本质不同对象 proxy.equals(raw)
true 代理重写了equals方法 proxy.hashCode()
与raw相同 代理委托给原始对象实现 proxy.getClass()
不同 代理是动态生成的子类/实现类
♋️生命周期示意图
markdown
1. Spring容器启动
↓
2. 创建原始PayService实例(非Bean)
↓
3. 创建代理包装该原始实例
↓
4. 注册代理对象 → 成为Spring Bean "payService"
↓
5. @Autowired PayService → 注入代理对象
通过理解这个设计,就能明白为什么你看到的"两个对象"其实是一个容器的代理对象和它内部的非托管原始对象的关系。
🔄 生命周期交互图解
sequenceDiagram
participant Container as Spring容器
participant Proxy as 代理对象
participant Target as 原始对象
Container->>Proxy: 创建代理对象
Proxy->>Target: 创建并持有原始对象
Container->>Proxy: 调用@PostConstruct
Proxy->>Target: 委托调用初始化方法
Container->>Proxy: 调用业务方法
Proxy->>Target: 委托执行业务逻辑
Container->>Proxy: 调用@PreDestroy
Proxy->>Target: 委托调用销毁方法
💎 结论
Spring 容器不会同时保存原始对象和代理对象作为独立 Bean。设计核心是:
- 唯一入口:只将代理对象注册为 Spring Bean
- 内部封装:原始对象作为代理的内部状态存在
- 透明操作:所有对 Bean 的操作都通过代理间接执行
这种设计保证了:
- AOP 功能的无缝集成
- 依赖注入的一致性
- 资源管理的优化
- 框架内部复杂性的屏蔽
如果您需要访问原始对象(通常不推荐),可以通过 AopUtils.getTargetObject(proxy)
或 Advised.getTargetSource()
方法获取,但请记住这违反了AOP的设计哲学。