1. Spring Bean Scope 基础概念解析
在Spring框架中,Bean的作用域(Scope)决定了Bean实例的生命周期和可见范围。理解不同Scope的创建机制对于构建健壮的Spring应用至关重要。Spring默认支持六种标准作用域,每种都有其独特的创建和销毁逻辑。
Spring 5.x版本中定义的标准作用域包括:
- singleton - 每个Spring IoC容器中一个Bean定义对应单个对象实例
- prototype - 一个Bean定义对应多个对象实例
- request - 一个Bean定义对应单个HTTP请求生命周期
- session - 一个Bean定义对应单个HTTP会话生命周期
- application - 一个Bean定义对应ServletContext生命周期
- websocket - 一个Bean定义对应WebSocket会话生命周期
提示:实际开发中最常用的是singleton和prototype,web相关作用域需要运行在Web环境中才能生效。
作用域的选择直接影响应用的以下方面:
- 内存占用和性能表现
- 线程安全考虑
- 依赖注入行为
- 生命周期回调执行时机
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Singleton作用域的创建机制
2.1 核心实现原理
Singleton是Spring默认的作用域,其核心特点是:
- 每个Spring容器中只存在一个共享实例
- 所有依赖注入都指向同一个对象
- 生命周期与容器一致
Spring通过三级缓存机制实现Singleton Bean的创建:
- 一级缓存(singletonObjects):存放完全初始化好的Bean
- 二级缓存(earlySingletonObjects):存放早期引用(解决循环依赖)
- 三级缓存(singletonFactories):存放Bean工厂对象
java复制// 伪代码展示Spring获取Singleton Bean的流程
public Object getSingleton(String beanName) {
// 1. 检查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {
// 2. 检查二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {
// 3. 检查三级缓存
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
return singletonObject;
}
2.2 初始化时机与线程安全
Singleton Bean的初始化时机有两种配置方式:
- 预初始化(默认):容器启动时立即创建
- 懒初始化:首次请求时创建
xml复制<!-- 显式声明singleton作用域 -->
<bean id="userService" class="com.example.UserService" scope="singleton"/>
<!-- 懒加载配置 -->
<bean id="lazyService" class="com.example.LazyService" lazy-init="true"/>
注意:虽然Singleton是线程不安全的,但Spring本身不解决线程安全问题。如果Bean需要被多线程访问,开发者需要自行实现同步机制。
3. Prototype作用域的创建机制
3.1 核心特点与实现
Prototype作用域的特点是:
- 每次请求都创建新实例
- Spring不管理完整的生命周期
- 依赖注入行为与Singleton不同
Prototype Bean的创建流程更简单:
- 容器收到获取Bean的请求
- 立即创建新实例
- 执行依赖注入
- 返回新创建的实例
java复制// 伪代码展示Prototype Bean创建流程
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) {
// 1. 实例化
Object beanInstance = instantiateBean(beanName, mbd);
// 2. 属性填充
populateBean(beanName, mbd, beanInstance);
// 3. 初始化
beanInstance = initializeBean(beanName, beanInstance, mbd);
return beanInstance;
}
3.2 使用场景与注意事项
Prototype适合以下场景:
- 需要维护状态的Bean
- 线程不安全对象的封装
- 每次需要新实例的业务逻辑
常见问题与解决方案:
- 性能问题:频繁创建销毁影响性能 → 考虑对象池
- 资源泄漏:Spring不调用销毁方法 → 手动管理资源
- 循环依赖:Prototype不支持构造器循环依赖 → 改用setter注入
java复制// 错误示例:Singleton依赖Prototype时的常见问题
@Service
public class SingletonService {
@Autowired
private PrototypeBean prototypeBean; // 始终是同一个实例
// 正确做法:使用方法注入或ApplicationContext.getBean()
}
4. Web相关作用域的创建机制
4.1 Request作用域实现
Request作用域Bean的特点:
- 每个HTTP请求创建新实例
- 请求结束时销毁
- 需要Web环境支持
Spring通过代理模式实现Request作用域:
- 容器启动时创建代理对象
- 每次方法调用时检查当前请求
- 不存在则创建新实例
- 将调用委托给实际实例
java复制// 注册Request作用域
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestScopedBean {
// 类实现
}
4.2 Session与Application作用域
Session作用域特点:
- 每个用户会话一个实例
- 会话超时或失效时销毁
- 需要配置会话监听器
Application作用域特点:
- 整个Web应用共享一个实例
- 介于Singleton和ServletContext之间
- 应用关闭时销毁
xml复制<!-- XML配置示例 -->
<bean id="userPreferences" class="com.example.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="appConfig" class="com.example.AppConfig" scope="application"/>
5. 自定义作用域实现
5.1 实现Scope接口
Spring允许注册自定义作用域,需要实现Scope接口:
java复制public interface Scope {
Object get(String name, ObjectFactory<?> objectFactory);
Object remove(String name);
void registerDestructionCallback(String name, Runnable callback);
Object resolveContextualObject(String key);
String getConversationId();
}
5.2 注册与使用自定义作用域
实现步骤:
- 编写Scope实现类
- 注册到Spring容器
- 在Bean定义中使用
java复制// 1. 实现自定义Scope
public class ThreadScope implements Scope {
private final ThreadLocal<Map<String, Object>> threadScope =
ThreadLocal.withInitial(ConcurrentHashMap::new);
@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
Map<String, Object> scope = threadScope.get();
return scope.computeIfAbsent(name, k -> objectFactory.getObject());
}
// 其他方法实现...
}
// 2. 注册自定义Scope
ConfigurableBeanFactory factory = (ConfigurableBeanFactory) applicationContext.getBeanFactory();
factory.registerScope("thread", new ThreadScope());
// 3. 使用自定义Scope
@Component
@Scope("thread")
public class ThreadScopedBean {
// 类实现
}
6. 作用域代理与依赖注入
6.1 代理模式选择
对于非Singleton作用域的Bean注入,Spring提供两种代理方式:
- JDK动态代理:基于接口(ScopedProxyMode.INTERFACES)
- CGLIB代理:基于类(ScopedProxyMode.TARGET_CLASS)
java复制// 代理模式配置示例
@Configuration
public class AppConfig {
@Bean
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public UserPreferences userPreferences() {
return new UserPreferences();
}
}
6.2 作用域注入的典型问题
常见问题场景:
- Singleton注入Prototype时的实例共享
- AOP代理与作用域代理的冲突
- 线程安全问题
解决方案对比表:
| 问题场景 | 传统方案 | 推荐方案 |
|---|---|---|
| Singleton需要获取Prototype实例 | ApplicationContext.getBean() | 方法注入(@Lookup) |
| Web作用域Bean注入非Controller | 直接注入 | 使用作用域代理 |
| 多线程访问作用域Bean | 同步控制 | 使用ThreadLocal作用域 |
java复制// 方法注入示例
public abstract class SingletonBean {
@Lookup
protected abstract PrototypeBean getPrototypeBean();
public void doWork() {
PrototypeBean prototypeBean = getPrototypeBean(); // 每次获取新实例
// 使用prototypeBean...
}
}
7. 作用域与Bean生命周期的交互
7.1 初始化与销毁回调
不同作用域的Bean生命周期回调执行时机:
| 作用域 | @PostConstruct | InitializingBean | @PreDestroy | DisposableBean |
|---|---|---|---|---|
| singleton | 创建后立即执行 | 创建后立即执行 | 容器关闭时 | 容器关闭时 |
| prototype | 创建后立即执行 | 创建后立即执行 | 不执行 | 不执行 |
| request | 每个请求创建后 | 每个请求创建后 | 请求完成时 | 请求完成时 |
| session | 会话首次使用时 | 会话首次使用时 | 会话超时时 | 会话超时时 |
7.2 作用域与Spring AOP的交互
AOP代理对作用域的影响:
- 代理对象的创建时机可能早于目标对象
- 作用域边界可能被代理隐藏
- 需要特别注意自调用问题
java复制// AOP与作用域交互示例
@Service
@Scope("prototype")
public class OrderService {
@Transactional
public void placeOrder(Order order) {
// 事务逻辑...
this.updateInventory(order); // 自调用,AOP失效
}
@Transactional
public void updateInventory(Order order) {
// 库存更新逻辑
}
}
经验:对于有状态的作用域Bean,建议将AOP代理配置与作用域代理配置保持一致,避免复杂的代理链。
8. 作用域选择的最佳实践
8.1 作用域选型决策树
根据业务需求选择作用域的决策流程:
- 是否需要维护状态?
- 是 → 考虑prototype或web作用域
- 否 → 考虑singleton
- 状态的作用范围?
- 请求级别 → request
- 用户级别 → session
- 应用级别 → application
- 线程级别 → 自定义thread作用域
- 性能要求?
- 高频创建 → 考虑对象池或singleton+ThreadLocal
- 低频访问 → 可按需选择
8.2 性能优化建议
针对不同作用域的优化策略:
-
Singleton:
- 避免大对象
- 注意线程安全
- 合理使用懒加载
-
Prototype:
- 考虑对象池模式
- 重用昂贵资源
- 避免过度创建
-
Web作用域:
- 合理设置会话超时
- 及时清理不再需要的属性
- 考虑分布式场景下的同步
java复制// 对象池模式示例
public class PrototypeBeanPool {
private final ObjectPool<PrototypeBean> pool;
public PrototypeBeanPool() {
this.pool = new GenericObjectPool<>(new PrototypeBeanFactory());
}
public PrototypeBean getBean() throws Exception {
return pool.borrowObject();
}
public void returnBean(PrototypeBean bean) {
pool.returnObject(bean);
}
private static class PrototypeBeanFactory extends BasePooledObjectFactory<PrototypeBean> {
@Override
public PrototypeBean create() {
return new PrototypeBean();
}
@Override
public PooledObject<PrototypeBean> wrap(PrototypeBean obj) {
return new DefaultPooledObject<>(obj);
}
}
}
在实际项目中,我通常会为频繁创建的Prototype Bean实现对象池,这可以显著减少GC压力。同时,对于Web作用域Bean,一定要确保它们不包含大对象或昂贵资源,因为用户量增长时这些Bean会快速消耗内存。
