Spring Bean的作用域
一、核心定义
- 原型作用域(Prototype Scope):
每次从Spring容器中请求该Bean时(例如通过getBean()
方法或依赖注入),容器都会创建一个新的实例。 - 默认作用域是单例(singleton)(整个应用共享一个实例),而原型作用域的Bean是“按需生产”的独立对象。
二、原型作用域 VS 单例作用域
特性 | 原型作用域(prototype) | 单例作用域(singleton) |
---|---|---|
实例数量 | 每次请求生成新实例 | 整个容器内仅一个共享实例 |
生命周期管理 | 容器不管理销毁阶段(需手动清理资源) | 容器负责初始化和销毁 |
适用场景 | 有状态的、需要隔离的Bean | 无状态的、线程安全的服务类 |
内存占用 | 可能较高(频繁创建实例) | 较低(复用实例) |
三、典型应用场景
1. 需要隔离状态的Bean
- 示例:购物车(每个用户需要一个独立实例)
@Component
@Scope("prototype") // 关键注解
public class ShoppingCart {private List<String> items = new ArrayList<>();public void addItem(String item) {items.add(item);}
}
- 若用单例作用域,所有用户会共享同一个购物车,导致数据混乱。
2. 线程不安全的对象
- 示例:数据库连接(每个线程需要独立连接)
<!-- XML配置示例 -->
<bean id="dbConnection" class="com.example.DatabaseConnection" scope="prototype"/>
3. 需要动态配置的Bean
- 示例:每次请求根据参数生成不同配置的实例
// 通过ApplicationContext获取新实例
ApplicationContext context = ...;
ReportGenerator report = context.getBean(ReportGenerator.class);
四、配置方式
1. 注解方式
- 使用
@Scope
注解指定作用域:
@Component
@Scope("prototype") // 或 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class MyPrototypeBean { ... }
2. XML配置
- 在Bean定义中添加
scope
属性:
<bean id="myBean" class="com.example.MyBean" scope="prototype"/>
五、生命周期特点
- 初始化:每次创建实例时调用
@PostConstruct
方法(如果有)。 - 销毁:容器不会调用原型Bean的
@PreDestroy
方法!- 原因:容器不跟踪原型Bean的生命周期,需自行释放资源(如关闭文件句柄、数据库连接)。
六、注意事项
-
性能影响:
- 频繁创建原型Bean可能增加内存和GC压力,需权衡是否真需要多实例。
-
与单例Bean的依赖关系:
- 若单例Bean依赖原型Bean,直接注入会导致原型Bean仅初始化一次(与预期不符)。
- 解决方案:通过
ObjectFactory
或Provider
延迟获取:
@Autowired
private ObjectFactory<PrototypeBean> prototypeBeanFactory;public void usePrototype() {PrototypeBean bean = prototypeBeanFactory.getObject();
}
3. 与线程安全问题:
- 原型作用域本身不保证线程安全,需结合其他机制(如
ThreadLocal
)控制。
七、代码验证示例
@SpringBootTest
public class PrototypeTest {@Autowiredprivate ApplicationContext context;@Testpublic void testPrototypeScope() {// 两次获取Bean实例Object bean1 = context.getBean("myPrototypeBean");Object bean2 = context.getBean("myPrototypeBean");// 断言两个实例不同Assert.assertNotSame(bean1, bean2); // 测试通过}
}
总结
- 原型作用域的核心:按需生成新实例,适用于有状态对象或需要隔离的场景。
- 慎用场景:避免在无状态服务或高频请求中使用,防止资源浪费。
- 生命周期责任:开发者需自行管理原型Bean的资源释放。
Spring 循环依赖
一、循环依赖解决:三级缓存机制
Spring 通过三级缓存解决单例 Bean 的循环依赖问题。三级缓存分别为:
- singletonObjects(一级缓存):存储完全初始化后的单例 Bean。
- earlySingletonObjects(二级缓存):存储已实例化但未完成属性注入的早期 Bean 引用。
- singletonFactories(三级缓存):存储 Bean 的
ObjectFactory
,用于生成早期对象(可能包含代理对象)。
解决流程示例(Bean A 依赖 Bean B,Bean B 依赖 Bean A):
- 实例化 Bean A,将其
ObjectFactory
放入 singletonFactories。 - 填充 A 的属性时,发现依赖 B,触发 B 的实例化。
- 实例化 Bean B,将其
ObjectFactory
放入 singletonFactories。 - 填充 B 的属性时,发现依赖 A,从 singletonFactories 获取 A 的
ObjectFactory
,生成早期引用并放入 earlySingletonObjects。 - B 完成初始化,移入 singletonObjects。
- A 获取 B 的实例后完成初始化,移入 singletonObjects。
关键点:
- 仅单例 Bean 适用,原型(prototype)作用域会直接报错。
- 若存在 AOP 代理,三级缓存通过
ObjectFactory
提前生成代理对象,确保依赖注入正确。
Spring事务传播机制
一、事务传播机制
Spring 定义了多种事务传播行为,核心类型如下:
-
PROPAGATION_REQUIRED(默认)
- 若当前存在事务,则加入该事务;否则新建事务。
- 示例:方法 A 调用方法 B,若 A 已开启事务,B 加入同一事务;若 A 无事务,B 新建事务。
-
PROPAGATION_REQUIRES_NEW
- 无论当前是否存在事务,始终新建事务,并挂起当前事务(若存在)。
- 示例:方法 A(事务 T1)调用方法 B(REQUIRES_NEW),B 开启新事务 T2,T1 被挂起。T2 提交后,T1 继续执行。
对比:
传播类型 | 事务独立性 | 回滚影响 |
---|---|---|
PROPAGATION_REQUIRED | 共享同一事务 | 任一操作失败,整体回滚 |
PROPAGATION_REQUIRES_NEW | 独立事务,外部事务挂起 | 内部事务回滚不影响外部,反之亦然 |
二、事务失效的常见场景
1. 非 public 方法
- 原因:Spring 基于代理实现事务,无法为
private
或protected
方法生成代理。 - 解决:确保事务方法为
public
。
2. 同类内调用
- 原因:通过
this
调用本类方法,未经过代理对象,事务拦截失效。 - 示例:
@Service
public class UserService {public void methodA() {this.methodB(); // 直接调用,事务失效}@Transactionalpublic void methodB() { ... }
}
- 解决:使用
@Autowired
注入自身代理或通过AopContext.currentProxy()
调用。
3. 异常被捕获未抛出
- 原因:Spring 默认在抛出未检查异常(如
RuntimeException
)时回滚。若异常被捕获且未重新抛出,事务不生效。 - 示例:
@Transactional
public void update() {try {// 数据库操作} catch (Exception e) {// 捕获异常未抛出 → 事务未回滚}
}
- 解决:抛出异常或设置
@Transactional(rollbackFor = Exception.class)
。
总结
- 循环依赖:三级缓存通过提前暴露 Bean 的早期引用,结合代理机制解决单例 Bean 循环依赖。
- 事务传播:根据业务需求选择传播行为,注意
PROPAGATION_REQUIRES_NEW
的事务独立性。 - 事务失效:避免非 public 方法、同类内直接调用及异常捕获不处理,确保事务正确生效。