In Spring, why does a service with scope prototype instantiate many times?

Viewed 279

I have created a Spring project and created the following files because I am trying to understand how Spring handles Singleton vs not-singleton Component classes:

Scheduler.java:

@Component
public class ScheduledTasks {

    @Autowired
    private TestRun testRun;

    @Scheduled(cron= "*/15 * * * * *")
    public void startTest() {
        testRun.startNewRun();
    }
}

TestRun.java

@Service
public class TestRun {

    private long t = System.currentTimeMillis();

    @Autowired
    private TestService testService;

    public void startNewRun() {
        new Thread(new TestRun.TestRunThread()).start();
    }

    public TestRun(){
        System.out.println("TestRun Constructor " + t);
    }

    public class TestRunThread implements Runnable {
        public void run() {
            testService.toString();
            System.out.println("Counta " + testService.getCount());
            System.out.println("Countb " + testService.getCount());
        }
    }
}

TestService.java

@Service
@Scope(value="prototype", proxyMode= ScopedProxyMode.TARGET_CLASS)
public class TestService {

    private long t = System.currentTimeMillis();

    private (volatile) int count; //Tried both with and without volatile

    public TestService(){
        System.out.println("Service Constructor " + t);
    }

    public int getCount(){
        return count++;
    }
}

My expectation after running this would be: Class TestRun will be instantiated once (true) and show 'TestRun Constructor {milliseconds}' (true) But although my expectation that the TestService constructor would be called multiple times (Every time a new Thread is started) is true, the following output I can not explain:

TestRun Constructor 1612706390893
...
TestService Constructor 1612706399976
Counta 0 ; 1612706390893
TestService Constructor 1612706400011
Countb 0 ; 1612706390893
TestService Constructor 1612706415006
Counta 0 ; 1612706390893
TestService Constructor 1612706415009
Countb 0 ; 1612706390893

The 0 following 'Counta' I understand, but why is the count after 'Countb' also 0? From the logging I conclude the TestService is instantiated again when calling the 'testService.getCount()' method, but I do not understand why this is the case. Any help would be appreciated.

1 Answers

The problem arises because you're using ScopedProxyMode.TARGET_CLASS with a prototype bean. Most likely CGLIB is generating a wrapper, and each call to TestService creates a new instance, e.g.

// CGLIB proxy
class TestServiceProxy extends TestService {
  ...

  public int getCount() {
    final TestService testService = ...
    return testService.getCount();
  }
}
 

I actually run an example for you.
You can see the debug call stack, and notice CGLIB generates a wrapper.

enter image description here

Removing ScopedProxyMode.TARGET_CLASS

@Service
@Scope("prototype")
public class TestService {

frees-up the call stack from proxies, and displays the expected result.

enter image description here

Related