2

I am studying Spring framework and I saw some thing that I couldn't explain what happens actually. suppose we have this simple Service class:

@Service
@Scope(scopeName = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RandomNumberGenerator {

  double number;
  public RandomNumberGenerator() {
    System.out.println("Constructor is called!");
    number = Math.random();
  }

  public double getNumber() {
    return number;
  }
}

and the following controller that uses above service:

@RestController
public class NumberController {

  @Autowired
  RandomNumberGenerator numberGenerator;

  @GetMapping(path = "/number")
  public double getNumber(){
    System.out.println(System.identityHashCode(numberGenerator));
    return numberGenerator.getNumber();
  }
}

I postulated that with every request a new RandomNumberGenerator numberGenerator instance will be created and so I should have different numbers printed in the System.out.println(System.identityHashCode(numberGenerator)); but strangely I got the same number with every request but I can see with every request constructor of the RandomNumberGenerator is called!

for example you can see my console output:

257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344
Constructor is called!
257434344

my question is why I get the same number even though constructor is called? isn't System.out.println(System.identityHashCode(numberGenerator)) should returns different integers?

hatef alipoor
  • 3,601
  • 1
  • 10
  • 28
  • @josejuan controller get us a different number but this line `System.out.println(System.identityHashCode(numberGenerator));` prints the same number. I added console output to question – hatef alipoor Sep 10 '21 at 11:48
  • Spring initializes the `numberGenerator` field only once with a proxy object that internally knows how to create new instances of the `RandomNumberGenerator` type and delegate `getNumber` calls to. However, the `identityHashCode` method applies to the proxy object, not to the new objects. – Sotirios Delimanolis Sep 10 '21 at 11:54
  • See https://stackoverflow.com/questions/58170627/what-is-a-scoped-proxy-in-spring – Sotirios Delimanolis Sep 10 '21 at 11:54
  • @josejuan The OP is under the impression that `numberGenerator` is always a new object, per request. – Sotirios Delimanolis Sep 10 '21 at 11:57
  • @josejuan I want a mechanism to differentiate between instances, I searched stackoverflow and they suggest use `System.identityHashCode` – hatef alipoor Sep 10 '21 at 12:00
  • @hatefAlipoor Can you explain why you're trying to differentiate between instances? – Sotirios Delimanolis Sep 10 '21 at 12:07
  • @SotiriosDelimanolis I am studying Spring framework and I thought I have a different `RandomNumberGenerator` instance with every request. but I did a test and I saw I only have one – hatef alipoor Sep 10 '21 at 12:10
  • You have one proxy instance, and then you have real instances for each request when invoking those instance methods like `getNumber`, but the real instances are not exposed to you here. – Sotirios Delimanolis Sep 10 '21 at 12:10
  • @SotiriosDelimanolis thank you very much! please post you answer and I happily accept that as answer. – hatef alipoor Sep 10 '21 at 12:14

1 Answers1

5

A lot has been written about how proxies are implemented in Spring, see my answers in the following posts:

In short, with ScopedProxyMode.TARGET_CLASS, Spring will use CGLIB to generate a subclass of RandomNumberGenerator. The class will have a name like RandomNumberGenerator$$EnhancerByCGLIB$$d0daae41.

This class overrides (almost) all the methods of RandomNumberGenerator to act as factories and delegators. Each of these overriden methods will produce a new instance (per request) of the real RandomNumberGenerator type and delegate the method call to it.

Spring will create an instance of this new CGLIB class and inject it into your @Controller class' field

@Autowired
RandomNumberGenerator numberGenerator;

You can call getClass on this object (one of the methods that is not overriden). You'd see something like

RandomNumberGenerator$$EnhancerByCGLIB$$d0daae41

indicating that this is the proxy object.

When you call the (overriden) methods

numberGenerator.getNumber()

That CGLIB class will use Spring's ApplicationContext to generate a new RandomNumberGenerator bean and invoke the getNumber() method on it.


The scopeName controls the scope of the bean. Spring will use RequestScope to handle these beans. If your controller called getNumber() twice, you should get the same value. The proxy (through the RequestScope) would internally cache the new, per-request object

@GetMapping(path = "/number")
public double getNumber(){
  System.out.println(numberGenerator.getNumber() == numberGenerator.getNumber()); // true
  return 123d;
}

If you had used a scope like "session", Spring would cache the real object across multiple requests. You can even use scope "singleton" and the object would be cached across all requests.


If you really needed to, you can retrieve the actual instance with the techniques described here

For example,

Object real = ((Advised)numberGenerator).getTargetSource().getTarget();

As for System.identityHashCode, you're calling it by passing the proxy object to it. There's only one proxy object, so the call will always return the same value.

Note that identityHashCode is not guaranteed to return different values for different objects. Its javadoc states

Returns the same hash code for the given object as would be returned by the default method hashCode(), whether or not the given object's class overrides hashCode(). The hash code for the null reference is zero.

and hashCode()'s javadoc states

It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results.

Sotirios Delimanolis
  • 263,859
  • 56
  • 671
  • 702