在 Spring 框架中,Bean 的默认作用域(Scope)是 单例的(Singleton)。
这意味着,在同一个 Spring IoC 容器中,针对一个特定的 Bean 定义,Spring 只会创建一个共享的实例。无论你通过 @Autowired 注入多少次,或者调用多少次 getBean(),获取到的都是同一个对象。
1. Spring Bean 的常见作用域对比
为了让你更清楚“单例”所处的位置,我们可以看这个对比表:
| 作用域 (Scope) | 是否默认 | 行为描述 |
| singleton | 是 | 单例:整个容器中只有一个实例。 |
| prototype | 否 | 原型:每次获取(注入或 getBean)都会创建一个新实例。 |
| request | 否 | 请求(Web环境):每个 HTTP 请求创建一个实例。 |
| session | 否 | 会话(Web环境):每个 HTTP 会话创建一个实例。 |
2. 为什么 Spring 默认选“单例”?
-
性能极高:不需要频繁地进行对象的创建和销毁(GC 压力小)。
-
减少内存消耗:对于 Controller、Service、DAO 这种逻辑组件,它们本身不保存客户状态,没必要创建多个。
-
方便管理:Spring 可以在启动时就预加载好这些单例,提高响应速度。
3. 单例模式的“坑”:线程安全问题
这是面试和实际开发中最需要注意的地方:Spring 的单例 Bean 并不是线程安全的。
-
有状态的 Bean(Stateful):如果你的 Service 类里定义了一个成员变量
private int count = 0;,当多个用户同时访问时,这个count会被互相覆盖,导致数据混乱。 -
无状态的 Bean(Stateless):如果你的类里只有方法逻辑(绝大多数 Controller 和 Service 都是这样),那么单例是绝对安全的。
💡 避坑建议: 在 Spring 的单例 Bean 中,尽量不要定义成员变量。如果一定要定义,请确保它是只读的,或者使用 ThreadLocal 来保证线程隔离。
4. 如何修改为非单例?
如果你确实需要每次都拿新对象,可以使用 @Scope 注解:
@Component
@Scope("prototype") // 修改为原型模式,每次注入都是新对象
public class MyService {
// ...
}
总结
Spring 创建的 Bean 默认是单例的。这符合“无状态服务”的设计理念,既省内存又跑得快。
既然聊到了单例和线程安全,你想看看在你的 BtcPrivateKeyServiceImpl 中,如果有成员变量,在高并发下会发生什么诡异的 Bug 吗?我可以为你演示一下如何用 ThreadLocal 来修复它。