Spring单例类加载多例属性问题实例解析

 更新时间:2026年03月06日 11:14:15   作者:Hui  Baby  
本文给大家介绍Spring单例类加载多例属性问题实例解析,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧

Spring 的 多例(Prototype)作用域 (scope="prototype") 实现“每次请求都生成新对象”的核心机制,其实非常简单直接:

它完全不缓存 Bean 实例。

与单例(Singleton)不同,Spring 容器内部没有一个 Map 来存储 Prototype Bean 的实例。每次你调用 getBean() 或注入该 Bean 时,Spring 都会重新执行完整的创建流程

1. 核心流程对比

为了让你看清区别,我们对比一下 Singleton 和 Prototype 的处理逻辑:

🔵 单例 (Singleton) - “查缓存,没有才造”

// 伪代码:Spring 内部逻辑
public Object getBean(String beanName) {
    // 1. 先查一级缓存 (singletonObjects)
    Object bean = singletonObjects.get(beanName);
    // 2. 如果有,直接返回 (秒回,不创建)
    if (bean != null) {
        return bean;
    }
    // 3. 如果没有,才创建,并放入缓存
    bean = createBean(beanName);
    singletonObjects.put(beanName, bean); // <--- 关键:存起来
    return bean;
}

结果:第一次创建,后续无数次都是返回同一个对象。

🟢 多例 (Prototype) - “不管有没有,每次都造”

// 伪代码:Spring 内部逻辑 (AbstractBeanFactory.doGetBean)
public Object getBean(String beanName) {
    // 1. 获取 Bean 定义
    RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
    // 2. 判断作用域
    if (mbd.isPrototype()) {
        // 【关键点】:直接进入创建流程,完全不查缓存!
        // 也不会在创建后存入任何缓存
        return createBean(beanName, mbd, args); 
    }
    // ... 单例的其他逻辑 ...
}

结果:每次调用 createBean,都是一次全新的生命周期(实例化 -> 属性填充 -> 初始化)。

2. 详细执行步骤

当你请求一个 Prototype Bean 时,Spring 内部会发生以下过程:

  1. 解析定义:读取 XML 或注解中的 @Scope("prototype") 配置。
  2. 跳过缓存检查:代码逻辑直接判定为多例,跳过 getSingleton() 这种缓存查找操作。
  3. 调用 createBean()
    • 实例化 (Instantiation):调用构造函数(或工厂方法)new YourClass()
    • 属性填充 (Populate):依赖注入 (@Autowired@Value)。
    • 初始化 (Initialization):执行 @PostConstructInitializingBean.afterPropertiesSet(), 自定义 init-method
  4. 返回对象:将创建好的全新对象返回给调用者。
  5. 放手不管Spring 容器不再持有该对象的引用
    • 这意味着:Spring 不会管理 Prototype Bean 的完整生命周期
    • 特别是:Spring 不会自动调用 @PreDestroy 或 destroy-method。因为容器不知道这个对象去哪了,也不知道什么时候该销毁它(由调用者负责垃圾回收)。

3. 源码级证据

在 Spring 的核心类 AbstractBeanFactory 的 doGetBean 方法中,有非常明确的逻辑分支:

// org.springframework.beans.factory.support.AbstractBeanFactory.java
protected <T> T doGetBean(...) {
    // ... 前置处理 ...
    // 判断作用域
    String scopeName = mbd.getScope();
    if (scopeName != null) {
        if (scopeName.equals(ConfigurableBeanFactory.SCOPE_PROTOTYPE)) {
            // 【核心代码】
            // 如果是 prototype,直接调用 createBean,没有任何缓存逻辑
            Object bean = createBean(beanName, mbd, args);
            return (T) bean;
        }
        // 如果是 request/session 等其它作用域,会使用 Scope 接口处理
        else if (scopeName.equals(ConfigurableBeanFactory.SCOPE_REQUEST)) {
             // ...
        }
    }
    // 如果是 singleton (默认),走下面的缓存逻辑
    // ... sharedInstance = getSingleton(...) ...
}

可以看到,对于 SCOPE_PROTOTYPE,代码路径是最短、最直接的,直接调用了创建方法。

4. 常见误区与坑

❌ 误区 1:以为注入到单例 Bean 中也能每次变新

这是最常见的错误!

@Component // 默认是 Singleton
public class UserService {
    @Autowired
    private ActionService actionService; // 假设 ActionService 是 Prototype
}
  • 现象UserService 是单例,它在 Spring 启动时只会被创建一次
  • 结果:在创建 UserService 的那一瞬间,Spring 注入了一个 ActionService 实例。此后,无论你怎么用 UserService,里面的 actionService 永远是同一个对象,不会变!
  • 原因:依赖注入发生在 Bean 创建时。单例 Bean 只创建一次,所以依赖也只注入一次。

✅ 解决方案:如何在一个单例中获取多例?

如果你需要在单例中每次都用新的 Prototype 对象,有三种方法:

方法注入 (Lookup Method Injection) - 推荐,纯注解
使用 @Lookup 注解,Spring 会在运行时动态重写这个方法,每次调用都去容器 getBean

@Component
public class UserService {
    @Lookup
    protected ActionService createActionService() {
        // 方法体可以是空的,Spring 会覆盖它
        return null; 
    }
    public void doWork() {
        ActionService action = createActionService(); // 每次都是新的
    }
}

注入 ObjectFactory 或 Provider - 推荐,标准写法
注入一个工厂对象,需要时手动 getObject()

@Component
public class UserService {
    @Autowired
    private ObjectProvider<ActionService> actionProvider;
    public void doWork() {
        ActionService action = actionProvider.getObject(); // 每次都是新的
    }
}

直接从 ApplicationContext 获取 - 不推荐,耦合度

@Autowired
private ApplicationContext context;
public void doWork() {
    ActionService action = context.getBean(ActionService.class);
}

     4.是要@Lazy注解标记多例对象

       @lazy启动时候创建代理类,在调用时候执行获取对象,生成代理对象,走getBean,由于标注多例,则会重写createBean创建新的对象

特性@Lazy 注入ObjectProvider 注入@Lookup 注解
能否获取新实例✅  (Spring 5+)✅ ✅ 
代码可读性⭐⭐⭐ (意图是“延迟”,多例是隐含的)⭐⭐⭐⭐⭐ (意图明确:“我要工厂”)⭐⭐⭐⭐ (意图明确,但需抽象方法)
灵活性⭐⭐ (只能直接调用方法)⭐⭐⭐⭐⭐ (可判断 ifAvailable, 流式操作 stream())⭐⭐ (只能获取)
底层原理AOP 代理 (JDK 或 CGLIB)依赖注入工厂对象CGLIB 方法重写
推荐指数推荐 (简单场景够用)强烈推荐 (最佳实践)推荐 (无参构造场景)

5. 总结

Spring 多例对象之所以能每次生成新的,是因为:

  1. 策略不同:它在 doGetBean 阶段就跳过了所有缓存检查
  2. 动作直接:直接调用 createBean() 执行完整的实例化和初始化流程。
  3. 无状态管理:创建完成后,容器立即断开引用,不保存、不跟踪、不销毁。

这就好比自动售货机(单例)和现做冰淇淋摊(多例):

  • 自动售货机:货都在柜子里(缓存),有人买直接拿,卖完了才补货。
  • 冰淇淋摊:每次有人买,老板都重新挖球、装筒(createBean),做完给你,手里不留货(不缓存)。

到此这篇关于spring单例类加载多例属性问题的文章就介绍到这了,更多相关spring单例类加载多例属性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • springBoot整合rabbitMQ的方法详解

    springBoot整合rabbitMQ的方法详解

    这篇文章主要介绍了springBoot整合rabbitMQ的方法详解,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • 浅谈Hibernate n+1问题

    浅谈Hibernate n+1问题

    这篇文章主要介绍了浅谈Hibernate n+1问题,怎么解决n+1问题,文中也作了简要分析,小编觉得还是挺不错的,具有一定借鉴价值,需要的朋友可以参考下
    2018-02-02
  • 详解java动态代理模式

    详解java动态代理模式

    这篇文章主要为大家详细介绍了java动态代理模式,总结一下代理模式,以及jdk,cglib代理模式用法,来理解代理模式,感兴趣的小伙伴们可以参考一下
    2016-02-02
  • Java多线程之原子类解析

    Java多线程之原子类解析

    这篇文章主要介绍了Java多线程之原子类解析,Java原子类是一种多线程编程中常用的工具,用于实现线程安全的操作,它们提供了一种原子性操作的机制,确保多个线程同时访问共享变量时的数据一致性,需要的朋友可以参考下
    2023-10-10
  • Maven 项目用Assembly打包可执行jar包的方法

    Maven 项目用Assembly打包可执行jar包的方法

    这篇文章主要介绍了Maven 项目用Assembly打包可执行jar包的方法,该方法只可打包非spring项目的可执行jar包,需要的朋友可以参考下
    2023-03-03
  • Spring Gateway基本使用示例小结

    Spring Gateway基本使用示例小结

    Springcloud Gateway使用了Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架,具体一些特征,本文结合实例代码对Spring Gateway使用给大家介绍的非常详细,感兴趣的朋友一起看看吧
    2023-11-11
  • Nacos作为配置中心注册监听器方法

    Nacos作为配置中心注册监听器方法

    本文主要讨论Nacos作为配置中心时,其中配置内容发生更改时,我们的应用程序能够做的事。一般使用监听器来实现这步操作,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习吧
    2023-02-02
  • java 使用idea将工程打成jar并创建成exe文件类型执行的方法详解

    java 使用idea将工程打成jar并创建成exe文件类型执行的方法详解

    这篇文章主要介绍了java 使用idea将工程打成jar并创建成exe文件类型执行,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-09-09
  • 详解Mybatis是如何把数据库数据封装到对象中的

    详解Mybatis是如何把数据库数据封装到对象中的

    这篇文章主要介绍了Mybatis是如何把数据库数据封装到对象中的,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • Spring AI TikaDocumentReader详解

    Spring AI TikaDocumentReader详解

    TikaDocumentReader是SpringAI中用于从多种格式文档中提取文本内容的组件,支持PDF、DOC/DOCX、PPT/PPTX和HTML等格式,它在构建知识库、文档处理和数据清洗等任务中非常有用
    2025-01-01

最新评论