I think the right answer is: That depends on used proxyMode
!
The Spring's prototype
scope works totally different in ScopedProxyMode.NO
(which is typically equals to ScopedProxyMode.DEFAULT
, unless a different default has been configured at the component-scan instruction level) and in ScopedProxyMode.TARGET_CLASS
(or ScopedProxyMode.INTERFACES
).
In Spring, when prototype
scoped bean declared with ScopedProxyMode.NO
, then it's behavior is really almost the same as CDI's default scope @Dependent
. The only difference is lifecycle management. In Spring, "configured destruction lifecycle callbacks are not called", while CDI manages full lifecycle of @Dependent
beans.
But, when prototype
scoped bean declared with ScopedProxyMode.TARGET_CLASS
(or ScopedProxyMode.INTERFACES
), then in Spring it's behavior is totally different from CDI's @Dependent
scope. prototype
beans is not bound in any way to lifecycle of instances where they are injected. If you inject such prototype bean into singleton bean, the proxy will be injected instead, and new bean instance will be created each time any proxy method will be invoked!
What does this means?
Look at this code:
public static class MyBean {
public MyBean() {
System.out.println(MyBean.class.getSimpleName() + " " + this + " created!");
}
public void doSomething() {
System.out.println(MyBean.class.getSimpleName() + " " + this + " doSomething() invoked!");
}
}
@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public MyBean myBean() {
return new MyBean();
}
@RestController
public static class MyController {
@Autowired
private MyBean myBean;
@GetMapping("/hello")
public String hello() {
myBean.doSomething();
myBean.doSomething();
myBean.doSomething();
return "Hello from " + MyController.class.getSimpleName();
}
}
How many different instances of myBean
will be created for one invoke of MyController.hello()
method? Do you think one? No! Three instances of myBean
will be created for one controller's method invocation. Each time when myBean.doSomething();
is called. One invocation of MyController.hello()
will print something like:
MyBean demo.DemoApplication$MyBean@30853c6d created!
MyBean demo.DemoApplication$MyBean@30853c6d doSomething() invoked!
MyBean demo.DemoApplication$MyBean@a0b81cf created!
MyBean demo.DemoApplication$MyBean@a0b81cf doSomething() invoked!
MyBean demo.DemoApplication$MyBean@4caa7eb1 created!
MyBean demo.DemoApplication$MyBean@4caa7eb1 doSomething() invoked!
All 6 lines just for one invocation of MyController.hello()
.
It is really different from CDI's @Dependent
scope, where injected instance of MyBean
will be created only one time for each instance where it will be injected. And it is also different even from Spring's prototype
scope with ScopedProxyMode.NO
.
For example, if you create another controller MyController2
, with code equal to MyController
(remember, Spring controllers always have singleton
scope) and change declaration of MyBean
to:
@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.NO)
public MyBean myBean() {
return new MyBean();
}
then only two instances of MyBean
will be created by Spring - one for each controller. And both will be created eagerly together with controllers. You will see something like this on application start:
MyBean demo.DemoApplication$MyBean@4e1800d0 created!
MyBean demo.DemoApplication$MyBean@5e5cedf8 created!
And something like this on each invoke of MyController.hello()
method (same MyBean
instance every time):
MyBean demo.DemoApplication$MyBean@4e1800d0 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@4e1800d0 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@4e1800d0 doSomething() invoked!
And something like this on each invoke of MyController2.hello()
method (same MyBean
instance every time):
MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@5e5cedf8 doSomething() invoked!
So in this sence it is not really a "prototype" bean. It is more like a bean without scope - it's scope is totally dependent on scope of enclosing bean (controller). Just like CDI's @Dependent
scope.
And remember: by default Spring prototype
beans created in proxy mode ScopedProxyMode.DEFAULT
, which is the same as ScopedProxyMode.NO
.
Be careful
Usually this is not what you expect from scope named prototype
. There are other ways to inject fresh copy of prototype bean into bean of more broader scope (i.e. singleton
). One of them: use ObjectProvider
.
With ObjectProvider
you can have a prototype bean in ScopedProxyMode.NO
, but inject ObjectProvider<MyBean> myBeanProvider
instead of just MyBean myBean
into controller.
That way, you can have controller method, that gets a fresh copy of MyBean
each time when it calls myBeanProvider.getObject()
:
@RestController
public static class MyController {
@Autowired
private ObjectProvider<MyBean> myBeanProvider;
@GetMapping("/hello")
public String hello() {
MyBean myBean = myBeanProvider.getObject();
myBean.doSomething();
myBean.doSomething();
myBean.doSomething();
return "Hello from " + MyController.class.getSimpleName();
}
}
This code will print:
MyBean demo.DemoApplication$MyBean@52689e05 created!
MyBean demo.DemoApplication$MyBean@52689e05 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@52689e05 doSomething() invoked!
MyBean demo.DemoApplication$MyBean@52689e05 doSomething() invoked!
on invoke of MyController.hello()
method.
Note: doSomething()
was invoked three times on the same instance of MyBean
!
On each call of MyController.hello()
method, another instance of MyBean
will be created - each time myBeanProvider.getObject()
invoked.
So, the basic rules:
- If you need a Spring bean that lazily created each time when some method
invoked on this bean, use
@Scope(value = "prototype", proxyMode =
ScopedProxyMode.TARGET_CLASS)
or @Scope(value = "prototype", proxyMode =
ScopedProxyMode.INTERFACES)
.
- If you need a Srping bean that created together with it's enclosing bean (no matter what scope it has), use
@Scope(value = "prototype", proxyMode =
ScopedProxyMode.NO)
.
- If you need a Spring bean that lazily created when you decide to create it, use
@Scope(value = "prototype", proxyMode =
ScopedProxyMode.NO)
and inject ObjectProvider
instead of bean itself.