You are currently viewing Spring: Injecting smaller scope bean into larger scope bean

Spring: Injecting smaller scope bean into larger scope bean

Let’s build a coffee machine and then find out what happened if you inject a smaller scope bean into a larger scope bean. Spoiler: In the last part I’ll tell you how to fix the coffee machine.

Resources

GitLab: springboot-prototype-scope-inside-singleton

The Problem

Let’s suppose that we have CoffeeMachine and DisposableCup components like below. We would like to achieve the effect that the CoffeeMachine always uses a new cup when brewing coffee, so that’s why we changed the default singleton to prototype scope on DisposableCup component.

package com.bettercoding.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CoffeeMachine {
    @Autowired
    public final DisposableCup disposableCup;

    public DisposableCup brewCoffee() {
        System.out.printf("Brewing coffee in [%s]\n", disposableCup.getName());
        return disposableCup;
    }
}
package com.bettercoding.demo;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.concurrent.atomic.AtomicInteger;

@Component
@Scope(scopeName = "prototype")
public class DisposableCup {
    private static final AtomicInteger TOTAL_CUPS = new AtomicInteger();
    private final String name;

    public DisposableCup() {
        this.name = "Disposable cup number " + TOTAL_CUPS.incrementAndGet();
    }

    public String getName() {
        return name;
    }
}
package com.bettercoding.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ScopesDemoApp implements CommandLineRunner {


    @Autowired
    private CoffeeMachine coffeeMachine;

    public static void main(String[] args) {
        SpringApplication.run(ScopesDemoApp.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        coffeeMachine.brewCoffee();
        coffeeMachine.brewCoffee();
        coffeeMachine.brewCoffee();
    }
}

But… When we run the application we got the result like below:

Brewing coffee in [Disposable cup number 1]
Brewing coffee in [Disposable cup number 1]
Brewing coffee in [Disposable cup number 1]

As you can see CoffeeMachine uses the same cup instance for each brewing coffee. So what’s wrong with the code? The problem is understanding how Spring inject’s dependencies. It does it only once – during bean creation process. It means that once injected reference doesn’t change through the whole bean’s life even if the reference’s life scope is shorter than the bean’s life scope it is injected to. There is no difference if you use @Autowierd on a class field or you inject dependencies via class constructor.

The solution

In order for the CoffeeMachine to always use a new cup when brewing coffee you need to inject ObjectFactory<DisposableCup> and then just get necessary component instance from the factory. So let’s modify the CoffeMachine componet

package com.bettercoding.demo;

import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class CoffeeMachine {
    @Autowired
    public ObjectFactory<DisposableCup> disposableCupObjectFactory;

    public DisposableCup brewCoffee() {
        DisposableCup aCup = disposableCupObjectFactory.getObject();
        System.out.printf("Brewing coffee in [%s]\n", aCup.getName());
        return aCup;
    }
}

The last thing to do is to run the application again to ensure that everything works like we expect.

Brewing coffee in [Disposable cup number 1]
Brewing coffee in [Disposable cup number 2]
Brewing coffee in [Disposable cup number 3]

That’s all what I’ve prepared for you in this tutorial, if I helped you, please consider sharing this post to help me gain a wider audience.
Thanks and I hope to see you in my next tutorial.