Spring的Bean原型模式下的使用

目录

1、问题原因

1.1、注入点只初始化一次

1.2、代理模式问题

1.3、使用不当的获取方式

2、解决方案

[2.1. ApplicationContext获取](#2.1. ApplicationContext获取)

[2.2. 使用ObjectProvider](#2.2. 使用ObjectProvider)

[2.3. 使用Lookup方法](#2.3. 使用Lookup方法)

[2.4. 使用Provider接口](#2.4. 使用Provider接口)

3、原型模式的应用场景

[1. 有状态的Bean](#1. 有状态的Bean)

[2. 线程不安全的对象](#2. 线程不安全的对象)

[3. 需要每次使用新实例的场景](#3. 需要每次使用新实例的场景)

[4. 需要避免副作用的场景](#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、注意事项

  1. 内存考虑:原型Bean不会自动销毁,需要确保不会造成内存泄漏

  2. 性能影响:频繁创建复杂对象可能影响性能

  3. 依赖管理:原型Bean注入单例Bean时要特别小心(通常使用方法注入)

  4. 测试复杂性:原型Bean可能增加测试的复杂性

正确使用原型模式可以解决许多与状态和线程安全相关的问题,但需要权衡其带来的资源开销。

相关推荐
一只小青团42 分钟前
Python之面向对象和类
java·开发语言
qq_529835351 小时前
ThreadLocal内存泄漏 强引用vs弱引用
java·开发语言·jvm
落笔画忧愁e1 小时前
扣子Coze飞书多维表插件添加数据记录
java·服务器·飞书
山海上的风3 小时前
Spring Batch终极指南:原理、实战与性能优化
spring·性能优化·batch·springbatch
秋千码途4 小时前
小架构step系列08:logback.xml的配置
xml·java·logback
飞翔的佩奇4 小时前
Java项目:基于SSM框架实现的旅游协会管理系统【ssm+B/S架构+源码+数据库+毕业论文】
java·数据库·mysql·毕业设计·ssm·旅游·jsp
时来天地皆同力.4 小时前
Java面试基础:概念
java·开发语言·jvm
超级小忍5 小时前
Spring AI ETL Pipeline使用指南
人工智能·spring
阿华的代码王国5 小时前
【Android】搭配安卓环境及设备连接
android·java