As we know Spring uses proxies to add functionality (@Transactional
and @Scheduled
for example). There are two options - using a JDK dynamic proxy (the class has to implement non-empty interfaces), or generating a child class using the CGLIB code generator. I always thought that proxyMode allows me to choose between a JDK dynamic proxy and CGLIB.
But I was able to create an example which shows that my assumption is wrong:
Case 1:
Singleton:
@Service
public class MyBeanA {
@Autowired
private MyBeanB myBeanB;
public void foo() {
System.out.println(myBeanB.getCounter());
}
public MyBeanB getMyBeanB() {
return myBeanB;
}
}
Prototype:
@Service
@Scope(value = "prototype")
public class MyBeanB {
private static final AtomicLong COUNTER = new AtomicLong(0);
private Long index;
public MyBeanB() {
index = COUNTER.getAndIncrement();
System.out.println("constructor invocation:" + index);
}
@Transactional // just to force Spring to create a proxy
public long getCounter() {
return index;
}
}
Main:
MyBeanA beanA = context.getBean(MyBeanA.class);
beanA.foo();
beanA.foo();
MyBeanB myBeanB = beanA.getMyBeanB();
System.out.println("counter: " + myBeanB.getCounter() + ", class=" + myBeanB.getClass());
Output:
constructor invocation:0
0
0
counter: 0, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$2f3d648e
Here we can see two things:
MyBeanB
was instantiated only once.- To add the
@Transactional
functionality forMyBeanB
, Spring used CGLIB.
Case 2:
Let me correct the MyBeanB
definition:
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyBeanB {
In this case the output is:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class test.pack.MyBeanB$$EnhancerBySpringCGLIB$$b06d71f2
Here we can see two things:
MyBeanB
was instantiated 3 times.- To add the
@Transactional
functionality forMyBeanB
, Spring used CGLIB.
Could you explain what is going on? How does proxy mode really work?
P.S.
I've read the documentation:
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link 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.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
but it is not clear for me.
Update
Case 3:
I investigated one more case, in which I extracted the interface from MyBeanB
:
public interface MyBeanBInterface {
long getCounter();
}
@Service
public class MyBeanA {
@Autowired
private MyBeanBInterface myBeanB;
@Service
@Scope(value = "prototype", proxyMode = ScopedProxyMode.INTERFACES)
public class MyBeanB implements MyBeanBInterface {
and in this case the output is:
constructor invocation:0
0
constructor invocation:1
1
constructor invocation:2
counter: 2, class=class com.sun.proxy.$Proxy92
Here we can see two things:
MyBeanB
was instantiated 3 times.- To add the
@Transactional
functionality forMyBeanB
, Spring used a JDK dynamic proxy.