Spring singleton注入ScopedProxyMode.TARGET_CLASS修饰的prototype Bean频繁生成新实例问题
现象根因解析
两个核心运行逻辑就能完全解释你测试中观察到的所有现象,没有特殊的框架bug:
1. ScopedProxyMode.TARGET_CLASS 的真实工作机制
当你给prototype Bean加上这个配置时,Spring并不会把原生的SimplePrototypeBean实例直接注入到依赖类里,而是会为这个Bean生成一个CGLIB代理对象:
- 这个代理对象本身是单例级别存在于容器中的,对应你日志里看到的
getPrototypeBean这个Bean名称 - 你定义的原生prototype Bean,对应日志里的
scopedTarget.getPrototypeBean,是没有任何代理的原生实例定义 - 这个代理对象不会持有任何固定的prototype实例引用,只要任何代码通过代理调用
SimplePrototypeBean的任意方法(包括toString、set、get),代理都会实时从容器中获取一个全新的prototype实例,再把方法调用转发到这个新实例上执行
这就是为什么你每次访问prototype Bean都会看到不同的hash值——你访问的根本不是同一个实例,而是代理每次临时拉取的新对象。
2. 你测试环节的预期偏差
- 所有标记为singleton的Bean,在同一个ApplicationContext中全局唯一,无论调用多少次
getBean()获取,拿到的都是同一个对象。你日志里两次输出的SimpleBean的hash值完全一致,就说明simpleBean1和simpleBean2是同一个实例,根本不存在两个独立的SimpleBean对象,自然也就没有“两个SimpleBean各持一个prototype”的基础。 - 你日志里出现的5个不同hash的
SimplePrototypeBean实例,完全对应代码里的方法调用次数:- 第一次进入
setTextToPrototypeBean打印prototypeBean(调用toString)→ 生成第一个新实例 - 执行
prototypeBean.setText(name)→ 生成第二个新实例,在这个实例上完成赋值后就被丢弃,没有任何引用留存 - 第二次进入
setTextToPrototypeBean打印prototypeBean(调用toString)→ 生成第三个新实例 - 执行第二次的
prototypeBean.setText(name)→ 生成第四个新实例,完成赋值后丢弃 - 两次调用
getTextFromPrototypeBean()、最后打印prototypeBean,又会分别生成新的空实例调用get方法,自然返回全是null。
- 第一次进入
调整方案实现预期效果
想要实现「持有固定prototype实例、不会每次访问都生成新对象」的效果,按以下步骤调整即可:
- 第一步:去掉prototype Bean上的
proxyMode=ScopedProxyMode.TARGET_CLASS配置。这个配置本身就是为“每次访问都动态匹配对应作用域的实例”设计的,和你要固定持有单个实例的目标完全冲突。 - 第二步:保持SimpleBean的singleton作用域不变,Spring会在初始化SimpleBean时,一次性从容器中获取一个prototype实例注入到字段中,这个引用会和SimpleBean实例绑定到整个生命周期结束,后续所有对
prototypeBean字段的访问,都是操作同一个固定实例,不会再生成新对象。 - 如果你确实需要多个独立的SimpleBean实例、每个对应持有一个专属的prototype实例,只需要把SimpleBean的作用域也修改为prototype,每次从容器获取SimpleBean时,容器会自动为新的SimpleBean关联一个全新的prototype实例,完全匹配你最初设想的“总共2个SimpleBean、2个prototype实例”的效果。
补充说明:
ScopedProxyMode.TARGET_CLASS的典型使用场景是单例Bean需要注入request/session这类和web作用域绑定的Bean,让单例Bean在不同用户请求/会话访问时,能自动匹配当前请求对应的作用域实例,而不是一上来就把初始化阶段的request实例固定绑死到单例里。如果把这个配置直接套在prototype Bean上,就会出现你现在遇到的“每次调用都生成新实例”的问题。
内容的提问来源于stack exchange,提问作者ever alian
相关产品推荐
相关产品推荐

