当前位置: 代码网 > it编程>编程语言>Java > Spring单例类加载多例属性问题实例解析

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

2026年03月06日 Java 我要评论
spring 的多例(prototype)作用域(scope="prototype") 实现“每次请求都生成新对象”的核心机制,其实非常简单直接:它完全不缓

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单例类加载多例属性内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com