The proxy generated for @Transactional
behavior serves a different purpose than the scoped proxies.
The @Transactional
proxy is one that wraps the specific bean to add session management behavior. All method invocations will perform the transaction management before and after delegating to the actual bean.
If you illustrate it, it would look like
main -> getCounter -> (cglib-proxy -> MyBeanB)
For our purposes, you can essentially ignore its behavior (remove @Transactional
and you should see the same behavior, except you won’t have the cglib proxy).
The @Scope
proxy behaves differently. The documentation states:
[…] you need to inject a proxy object that exposes the same
public interface as the scoped object but that can also retrieve the
real target object from the relevant scope (such as an HTTP request)
and delegate method calls onto the real object.
What Spring is really doing is creating a singleton bean definition for a type of factory representing the proxy. The corresponding proxy object, however, queries the context for the actual bean for every invocation.
If you illustrate it, it would look like
main -> getCounter -> (cglib-scoped-proxy -> context/bean-factory -> new MyBeanB)
Since MyBeanB
is a prototype bean, the context will always return a new instance.
For the purposes of this answer, assume you retrieved the MyBeanB
directly with
MyBeanB beanB = context.getBean(MyBeanB.class);
which is essentially what Spring does to satisfy an @Autowired
injection target.
In your first example,
@Service
@Scope(value = "prototype")
public class MyBeanB {
You declare a prototype bean definition (through the annotations). @Scope
has a proxyMode
element which
Specifies whether a component should be configured as a scoped proxy
and if so, whether the proxy should be interface-based or
subclass-based.Defaults to
ScopedProxyMode.DEFAULT
, which typically indicates that no
scoped proxy should be created unless a different default has been
configured at the component-scan instruction level.
So Spring is not creating a scoped proxy for the resulting bean. You retrieve that bean with
MyBeanB beanB = context.getBean(MyBeanB.class);
You now have a reference to a new MyBeanB
object created by Spring. This is like any other Java object, method invocations will go directly to the referenced instance.
If you used getBean(MyBeanB.class)
again, Spring would return a new instance, since the bean definition is for a prototype bean. You’re not doing that, so all your method invocations go to the same object.
In your second example,
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
you declare a scoped proxy that is implemented through cglib. When requesting a bean of this type from Spring with
MyBeanB beanB = context.getBean(MyBeanB.class);
Spring knows that MyBeanB
is a scoped proxy and therefore returns a proxy object that satisfies the API of MyBeanB
(ie. implements all its public methods) that internally knows how to retrieve an actual bean of type MyBeanB
for each method invocation.
Try running
System.out.println("singleton?: " + (context.getBean(MyBeanB.class) == context.getBean(MyBeanB.class)));
This will return true
hinting to the fact that Spring is returning a singleton proxy object (not a prototype bean).
On a method invocation, inside the proxy implementation, Spring will use a special getBean
version that knows how to distinguish between the proxy definition and the actual MyBeanB
bean definition. That’ll return a new MyBeanB
instance (since it’s a prototype) and Spring will delegate the method invocation to it through reflection (classic Method.invoke
).
Your third example is essentially the same as your second.