Search code examples
springdependency-injection

Simple Situation! Weird Result! Spring prototype bean don't work, and every time I call getter or setter, the constructor was called


I am learning Spring and using XML-way to create 2 beans instances of a POJO Class named "Notepad". finally I got two instances:

n1 = Notepad@56dc1551,hashCode=267513570
n2 = Notepad@102cec62,hashCode=267513570

different toString @value, same hashCode!

Notepad.java

public class Notepad {
    private int a;
    public Notepad() {
        System.out.println("  constructor called. this="+this+" hashCode="+this.hashCode());
    }

    public int getA() {
        System.out.println("  this="+ this +" getA(),a="+a);
        return a;
    }
    public void setA(int a) {
        System.out.println("  this="+ this +" setA("+a+")");
        this.a = a;
    }

}

scope-beans.xml

...
    <bean id="notepad" class="Notepad" scope="prototype">
        <aop:scoped-proxy />
    </bean>
...

Test.java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:scoped-beans.xml")
public  class Test {
    
    @Autowired
    private ApplicationContext context;
    
    @Test
    public void testProxyScope() {
      System.out.println("containsBean(notepad)?"+context.containsBeanDefinition("notepad"));
      var n1 = context.getBean("notepad",Notepad.class);
      var n2 = context.getBean("notepad",Notepad.class);
      n1.setA(1);
      n2.setA(2);
      System.out.println("n1="+n1+" n1.a="+n1.getA());
      System.out.println("n2="+n2+" n2.a="+n2.getA());
      System.out.println("n1="+n1+",hashCode="+n1.hashCode());
      System.out.println("n2="+n2+",hashCode="+n2.hashCode());
      System.out.println("notepad1==notepad2? "+(n1==n2)); //???
      assertNotSame(n1, n2); //???
    }
}

test result in here

containsBean(notepad)?true
  constructor called. this=chp3.myapp.Notepad@4c2bb6e0 hashCode=1277933280
  this=chp3.myapp.Notepad@4c2bb6e0 setA(1)
  constructor called. this=chp3.myapp.Notepad@2dc9b0f5 hashCode=768192757
  this=chp3.myapp.Notepad@2dc9b0f5 setA(2)
  constructor called. this=chp3.myapp.Notepad@6531a794 hashCode=1697752980
  this=chp3.myapp.Notepad@6531a794 getA(),a=0
  constructor called. this=chp3.myapp.Notepad@3b5fad2d hashCode=996125997
n1=chp3.myapp.Notepad@3b5fad2d n1.a=0
  constructor called. this=chp3.myapp.Notepad@5e17553a hashCode=1578587450
  this=chp3.myapp.Notepad@5e17553a getA(),a=0
  constructor called. this=chp3.myapp.Notepad@3eb91815 hashCode=1052317717
n2=chp3.myapp.Notepad@3eb91815 n2.a=0
  constructor called. this=chp3.myapp.Notepad@56dc1551 hashCode=1457263953
n1=chp3.myapp.Notepad@56dc1551,hashCode=267513570
  constructor called. this=chp3.myapp.Notepad@102cec62 hashCode=271379554
n2=chp3.myapp.Notepad@102cec62,hashCode=267513570
notepad1==notepad2? true

java.lang.AssertionError: expected not same

I am expecting only create 2 instance, n1 != n2. but seems n1 n2 created multiple time, even when I do get and set! And weirdly, n1 == n2 finally! the code is very simple, I have no idea where the problem is...


Solution

  • The <aop:scoped-proxy /> is the problem. It does exactly what you told it to do. Create a proxy for the given scope. Which means each time it is being used it will create an instance (due to the scope="prototype".

    Leave the scope="prototype" and remove the <aop:scoped-proxy /> as you don't want a scoped proxy, you want that each time you call getBean get a fresh instance for use.

    Scoped proxies are useful for web based scoped (like request, session). That way you can inject a reference and when needed it will actually delegate to the actual object in either the request or session. For prototype that means for each call to a method it would need to construct a new instance (which is what you see).

    The call to getBean will actually return the proxy and not an actual instance of the object. That will only be created when needed, which is when calling the methods.