Issue
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?
Solution
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 overrideshashCode()
. 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.
Answered By - Sotirios Delimanolis
Answer Checked By - Katrina (JavaFixing Volunteer)