7

Is the Spring prototype scope the same as the CDI dependant scope.

Googling lead me to blog posts that claimed they are the same and others that claimed that they are similar but not quite the same without explaining the differences.

So what are the differences between the spring prototype scope and the cdi dependant scope?

Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
ams
  • 60,316
  • 68
  • 200
  • 288

2 Answers2

10

According to the CDI documentation and javadoc

When a bean is declared to have @Dependent scope:

  • No injected instance of the bean is ever shared between multiple injection points.
  • ...

Similarly, the Spring documentation states

The non-singleton, prototype scope of bean deployment results in the creation of a new bean instance every time a request for that specific bean is made.

They are, behaviorally, the same.

The only difference I could find is on the lifecycle of the bean. In Spring

Thus, although initialization lifecycle callback methods are called on all objects regardless of scope, in the case of prototypes, configured destruction lifecycle callbacks are not called. The client code must clean up prototype-scoped objects and release expensive resources that the prototype bean(s) are holding.

In CDI however the container manages the full lifecycle of the bean, either directly when it is injected as a method invocation argument or indirectly when destroying the bean it was injected into. The conditions are all described in the documentation linked.

As Luiggi mentions in the comments, it is important to note the default scope of a bean declaration. In the Spring docs state

The singleton scope is the default scope [...]

while in CDI, the default scope is dependent.

Community
  • 1
  • 1
Sotirios Delimanolis
  • 274,122
  • 60
  • 696
  • 724
  • From the doc you posted: *When a Unified EL expression in a JSF or JSP page that refers to the bean by its EL name is evaluated, at most one instance of the bean is instantiated. This instance exists to service just a single evaluation of the EL expression. It is reused if the bean EL name appears multiple times in the EL expression, but is never reused when the EL expression is evaluated again, or when another EL expression is evaluated.* does this also happen with prototype beans in Spring MVC? – Luiggi Mendoza Nov 18 '13 at 02:53
  • @LuiggiMendoza Spring doesn't work directly in EL with JSF and JSP so I don't think it applies there. If you are referring to Spring EL, take a look at the example in [8.4.1 of the Spring doc](http://docs.spring.io/spring/docs/3.2.4.RELEASE/spring-framework-reference/html/expressions.html), if `numberGuess` and `shapeGuess` were declared with `scope="prototype"` and you requested X `shapeGuess` beans, each initialization would require a new `numberGuess` bean to satisfy the `numberGuess`'s `initialShapeSeed` property. – Sotirios Delimanolis Nov 18 '13 at 03:02
  • So, in Spring terms, if you have a `@Prototype` bean instance, let's say A, injected in a `@Singleton` bean instance, let's say B, then A will live until B dies. But, in CDI terms, the `@Dependent` bean instance, let's say A', is injected in an `@ApplicationScoped` bean instance, let's say B', then B' will live until A' dies or B' will be reinitialized implicitly depending on usage of A', for example, different requests?. – Luiggi Mendoza Nov 18 '13 at 03:15
  • @Luiggi For Spring, yes. For CDI, unless I misunderstood the question, the CDI doc states `Any instance of the [prototype] bean injected into an object that is being created by the container is bound to the lifecycle of the newly created object.` So `A'` is bound to `B'`'s lifecycle and will be destroyed when `B'` is destroyed. – Sotirios Delimanolis Nov 18 '13 at 03:22
  • My Spring sentence wasn't a question, but a confirmation of the behavior (note that there's no `?` mark :) ). And for CDI, I found the answer after posting my comment, here: [The Java EE 7 tutorial: 23 Introduction to Contexts and Dependency: 23.8 Using Scopes](http://docs.oracle.com/javaee/7/tutorial/doc/cdi-basic008.htm#GJBBK). But, in short, as you posted, their behavior is basically the same. Now, apart from the question, Spring define the beans as `@Singleton` by default, while CDI define them as `@Dependent` (`@Prototype`) by default. – Luiggi Mendoza Nov 18 '13 at 03:27
  • @luiggi That is a good point. I've added it and some references in my answer. Thanks. – Sotirios Delimanolis Nov 18 '13 at 03:32
  • Since creation is equivalent, the point here is when Prototype is destroyed. @Luiggi says "A will live until B dies". Is this documented somewhere? – Rafael Nov 20 '13 at 16:23
  • I'm having trouble finding documentation around the behavior in Spring when using CDI annotations. Spring accepts `@Named` in place of `@Component`, but it isn't clear what the default scope is at that point. If you're using CDI annotations in Spring, do you still need `@Singleton`? – Anthony Naddeo Oct 10 '17 at 20:02
  • 1
    @AnthonyNaddeo See [here](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-standard-annotations-limitations). A Spring bean being registered through `@Named` will be a singleton by default. – Sotirios Delimanolis Oct 10 '17 at 20:09
0

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.
Ruslan Stelmachenko
  • 4,987
  • 2
  • 36
  • 51