目录
[2.1. ApplicationContext获取](#2.1. ApplicationContext获取)
[2.2. 使用ObjectProvider](#2.2. 使用ObjectProvider)
[2.3. 使用Lookup方法](#2.3. 使用Lookup方法)
[2.4. 使用Provider接口](#2.4. 使用Provider接口)
[1. 有状态的Bean](#1. 有状态的Bean)
[2. 线程不安全的对象](#2. 线程不安全的对象)
[3. 需要每次使用新实例的场景](#3. 需要每次使用新实例的场景)
[4. 需要避免副作用的场景](#4. 需要避免副作用的场景)
前言
原型(Prototype)模式的Bean在Spring中每次请求时都会创建一个新的实例,这与单例(Singleton)模式形成对比。
如下图所示:


默认情况下,Spring中的Bean采用单例模式,即容器始终返回同一实例。然而,通过将singleton属性设置为false,可以使得每次请求都返回一个新的Bean实例。
当你在Spring中使用原型(prototype)作用域的Bean时,如果通过@Autowired
注入后发现仍然是单例行为。
1、问题原因
1.1、注入点只初始化一次
@Autowired
在依赖注入时只发生一次(在Bean创建时),之后每次访问都是同一个实例。
1.2、代理模式问题
如果原型Bean被代理(如AOP代理),而代理配置不正确,可能导致每次获取的都是同一个代理实例。
1.3、使用不当的获取方式
直接通过
@Autowired
注入原型Bean实际上违背了原型模式的设计初衷。
总结:
@Autowired
在依赖注入时只执行一次,将原型Bean注入到单例Bean中时,这个注入动作只发生一次,因此单例Bean持有的始终是同一个原型Bean实例。这与原型Bean的设计初衷相违背。
正确的方式应该是每次需要新实例时都从容器中显式获取,而不是在初始化时就注入并持有。
2、解决方案
2.1. ApplicationContext获取
java
@Autowired
private ApplicationContext applicationContext;
public void someMethod() {
PrototypeBean bean = applicationContext.getBean(PrototypeBean.class);
// 每次调用getBean()都会返回新实例
}
2.2. 使用ObjectProvider
使用(Spring 4.3+)版本,代码示例如下:
java
@Autowired
private ObjectProvider<PrototypeBean> prototypeBeanProvider;
public void someMethod() {
PrototypeBean bean = prototypeBeanProvider.getObject();
// 每次getObject()都会返回新实例
}
2.3. 使用Lookup方法
通过@lookup注解,重写get${method}方法。
代码示例如下:
java
@Configuration
public class AppConfig {
@Bean
@Scope("prototype")
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
@Component
public class SingletonBean {
// 使用方法注入
@Lookup
public PrototypeBean getPrototypeBean() {
return null; // Spring会覆盖此方法实现
}
public void someMethod() {
PrototypeBean bean = getPrototypeBean(); // 每次调用都返回新实例
}
}
2.4. 使用Provider接口
Provider接口是位于(javax.inject)包下。
代码实现如下:
java
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public void someMethod() {
PrototypeBean bean = prototypeBeanProvider.get();
// 每次get()都会返回新实例
}
3、原型模式的应用场景
1. 有状态的Bean
当Bean需要维护状态,且不同使用者需要独立的状态时:
-
用户会话相关的对象
-
请求处理过程中的上下文对象
-
购物车实例
-
表单数据对象
java
@Scope("prototype")
@Component
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
// 每个用户需要自己的购物车实例
}
2. 线程不安全的对象
当Bean不是线程安全的,且需要在多线程环境中使用时:
-
简单的日期格式化器(SimpleDateFormat)
-
随机数生成器
-
某些第三方库的非线程安全类
java
@Scope("prototype")
@Component
public class DateFormatter {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 每个线程需要自己的实例
}
3. 需要每次使用新实例的场景
-
临时计算器对象
-
事务处理器(某些场景下)
-
原型设计模式的实际应用
java
@Scope("prototype")
@Component
public class ReportGenerator {
// 每次生成报告都需要新的实例来保持独立性
}
4. 需要避免副作用的场景
当Bean的操作会修改内部状态,且这种修改不应该影响其他使用者时:
-
数据处理器
-
文件处理器
-
临时缓存对象
java
@Scope("prototype")
@Component
public class FileProcessor {
private File currentFile;
// 每个文件处理需要独立的处理器实例
}
4、注意事项
-
内存考虑:原型Bean不会自动销毁,需要确保不会造成内存泄漏
-
性能影响:频繁创建复杂对象可能影响性能
-
依赖管理:原型Bean注入单例Bean时要特别小心(通常使用方法注入)
-
测试复杂性:原型Bean可能增加测试的复杂性
正确使用原型模式可以解决许多与状态和线程安全相关的问题,但需要权衡其带来的资源开销。