Bean 作用域与使用注意

结论先行:Bean 默认作用域是 singleton(每个容器内每个 beanName 只有一个实例);prototype 每次获取都 new 一个新实例;Web 环境另有 request / session / application / websocket。使用注意三条:① 大对象/有状态对象慎用 singleton 做共享可变状态;② 长生命周期 Bean 注入短生命周期 Bean(如 singleton 注入 prototype/session)时,必须用作用域代理或每次现取,否则拿到的永远是第一个实例;③ prototype Bean 不受容器销毁回调管理。

一、作用域速查

作用域生命周期说明
singleton(默认)容器启动/首次创建,容器关闭销毁每容器每 beanName 一个实例,共享需防并发
prototype每次 getBean/注入创建容器不跟踪其销毁(destroy 回调不执行),需自行清理
request一次 HTTP 请求仅 Web 容器,请求结束销毁
session一次 HTTP 会话仅 Web 容器
application / websocketServletContext / WebSocket 生命周期仅 Web 容器

二、跨作用域注入的坑

// 正确做法:在 Bean 定义处声明"作用域 + 代理模式"(也可用 @SessionScope/@RequestScope 便捷注解)
@Component
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)   // 生成 CGLIB 作用域代理
public class LoginContext {
    private Long userId;
    // getter/setter ...
}

@Component
public class UserService {
    // singleton 注入 session Bean:实际拿到的是"作用域代理",
    // 每次调用方法都委托给当前会话的真实 LoginContext,而不是固定第一个用户的实例
    @Autowired
    private LoginContext ctx;
}
// 错误做法:LoginContext 本身是 singleton,却想在注入点用 @Scope 改变它 → 无效且误导
// 备选:注入 ObjectProvider<LoginContext>,调用时 getObject() 每次现取
// 备选:prototype 场景可用 @Lookup 方法注入,每次调用返回新实例

三、使用注意清单

  • singleton 中的共享可变字段有线程安全问题,应设计为无状态或使用 ThreadLocal/加锁;
  • prototype 每次新建有开销,不要在循环里高频 getBean;
  • 定义作用域:XML 用 scope 属性,注解用 @Scope("prototype"),常量建议 ConfigurableBeanFactory.SCOPE_PROTOTYPE;
  • 验证方式:容器 getBean 两次,prototype 的 hashCode 不同、singleton 相同。
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public TaskWorker taskWorker() { return new TaskWorker(); }

常见追问 / 记忆点

  • 追问 1:Spring 的 singleton 与 GoF 单例模式一样吗?答:不一样——Spring 单例是"容器作用域内每 beanName 一个实例",不是 JVM 全局,且由容器管理生命周期。
  • 追问 2:singleton 注入 prototype 依赖为什么不生效?答:singleton 在启动时实例化并固定注入引用,之后不再重新注入;解法是作用域代理/ObjectProvider/@Lookup。
  • 记忆点:"默认单例省心但共享要防并发;跨作用域注入必上代理或现取"。
笔记加载中…