
spring 默认创建的单例 bean 是全局共享的,若在其中修改可变状态(如实例变量),将导致多线程间数据污染;正确做法是保持 bean 无状态,或使用 threadlocal、作用域(如 prototype)隔离上下文。
在 Spring 应用中,绝大多数 Bean(如 @Service、@Repository、@Component)默认以 singleton 作用域创建——即整个 Spring 容器中仅存在一个实例,被所有请求、线程和调用共享。这意味着:如果你在某个 HTTP 请求(或任意线程)中修改了该 Bean 的可变实例字段(如 private String currentUser;),该修改会立即对其他并发请求可见,造成严重的线程安全问题和上下文污染。
❌ 危险示例:在单例 Bean 中维护可变状态
@Service
public class OrderService {
private String currentTraceId; // ❌ 共享的可变状态 —— 危险!
public void processOrder(String traceId) {
this.currentTraceId = traceId; // 多线程下互相覆盖!
log.info("Processing with trace: {}", currentTraceId);
// 后续业务逻辑可能误用被覆盖的 currentTraceId
}
}⚠️ 上述代码在高并发场景下会导致 currentTraceId 被不同请求交替写入,A 请求的日志可能打印出 B 请求的 trace ID,调试与追踪完全失效。
✅ 正确实践:保持 Bean 无状态(推荐)
Spring Bean 应设计为 stateless(无状态):只包含不可变配置、注入的依赖及纯业务逻辑方法,所有请求相关数据应通过方法参数传递:
@Service
public class OrderService {
private final PaymentGateway gateway; // 依赖注入(不可变)
public OrderService(PaymentGateway gateway) {
this.gateway = gateway;
}
// ✅ 状态由参数携带,线程安全
public OrderResult processOrder(String traceId, OrderRequest request) {
MDC.put("traceId", traceId); // 日志上下文用 MDC(ThreadLocal 实现)
return gateway.execute(traceId, request);
}
}✅ 替代方案(按需选用)
- @Scope("prototype"):每次 getBean() 或注入时新建实例(适合有状态的辅助对象,但需注意性能与生命周期管理);
-
ThreadLocal
:为每个线程绑定独立副本(如用户认证上下文),但务必在请求结束时 remove() 防止内存泄漏; - WebMvc 场景专用:使用 @RequestScope 或 @SessionScope Bean,由 Spring 自动管理生命周期;
- 函数式/响应式编程:将上下文作为显式参数(如 Mono.just(request).contextWrite(...))传递,彻底规避共享状态。
总结
Spring 的核心设计哲学是“依赖注入 + 无状态组件”。单例 Bean 不是存储请求上下文的容器,而是协调者与执行者。任何试图在 singleton Bean 中缓存请求级数据的行为,都会破坏线程安全性、可预测性和可测试性。请始终遵循:状态随请求走,Bean 只管逻辑。










