Spring原型Bean异常:调用Getter/Setter重复触发构造,实例判定不符合预期
兄弟你遇到的这个问题其实是Spring中prototype scope结合<aop:scoped-proxy/>的特性导致的,我来给你拆解清楚:
问题根源1:为什么构造函数被反复调用?
当你给prototype类型的Bean加上<aop:scoped-proxy/>时,Spring并不会直接返回Notepad的实例,而是返回一个动态代理对象。
这个代理的行为是:每次你调用代理对象的任何方法(包括getA()、setA(),甚至toString()、hashCode()这些Object类的方法),它都会去ApplicationContext中重新获取一个全新的Notepad原型实例,然后调用这个新实例的对应方法。
所以你看到的构造函数反复触发,本质是每次方法调用都在创建新的原型Bean实例,这正是这个代理的设计逻辑——确保每次方法执行都用的是最新的原型实例。
问题根源2:为什么n1 == n2返回true,断言失败?
因为你通过context.getBean("notepad")拿到的n1和n2,都是同一个代理对象,而不是真正的Notepad实例。代理对象本身是单例的,所以用==比较自然会返回true,导致你的assertNotSame(n1, n2)断言失败。
但实际上,每次通过代理调用方法时,背后的实际Notepad实例都是不同的——你看测试日志里setA(1)用的是@4c2bb6e0,setA(2)用的是@2dc9b0f5,这两个就是完全不同的原型实例。
解决办法
根据你的需求场景,有两种处理方式:
1. 不需要代理场景:去掉<aop:scoped-proxy/>
如果你的场景只是单纯想每次getBean获取新的原型实例,不需要在单例Bean中注入原型Bean并保持每次调用都新实例的话,直接删掉<aop:scoped-proxy/>即可:
<bean id="notepad" class="Notepad" scope="prototype"> <!-- 移除这行代理配置 --> </bean>
修改后,每次context.getBean()都会返回全新的Notepad实例,构造函数只会调用两次,n1和n2会是不同的对象,assertNotSame(n1, n2)也会正常通过。
2. 需要代理场景:理解代理行为并调整验证逻辑
如果你确实需要使用scoped-proxy(比如在单例Bean中注入原型Bean,希望每次调用方法都拿到新实例),那就要接受代理对象是单例的事实:
n1 == n2为true是正常的,因为它们是同一个代理- 要验证实际的原型实例是否不同,可以通过日志里的实例ID(比如
toString()输出的@后面的哈希值),或者在Notepad类中添加一个唯一标识属性(比如在构造函数中生成UUID)来区分。
比如修改Notepad类:
public class Notepad { private int a; private String uuid = UUID.randomUUID().toString(); public Notepad() { System.out.println(" constructor called. this="+this+" hashCode="+this.hashCode()+" uuid="+uuid); } // 其他getA/setA方法不变 }
这样每次构造的实例UUID都不同,就能明确看到每次方法调用的实例都是新的。
备注:内容来源于stack exchange,提问作者Sydney Chen

