ams
ams

Reputation: 62652

What is the difference between Spring Prototype scope and the CDI Dependent Scope?

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?

Upvotes: 7

Views: 5357

Answers (2)

Ruslan Stelmachenko
Ruslan Stelmachenko

Reputation: 5420

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.

Upvotes: 0

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 279970

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.

Upvotes: 10

Related Questions