DEV Community

Yonatan Karp-Rudin
Yonatan Karp-Rudin

Posted on • Originally published at yonatankarp.com on

How to inject multiple implementations in Spring Framework

x9lxt90krephvthizchu.png

Recently, I had a discussion with one of my colleagues during a code review. We talked about a hidden gem in Spring (as well as other frameworks like Micronaut) that could simplify our code.

Let's consider a task: developing a good morning greeting system that supports both English and Hebrew languages. The system should greet us in each of these languages.

We'll begin with the native implementation by introducing our greeters:

class HebrewGreeter {
    fun sayGoodMorning() {
        println("בוקר טוב")
    }
}

class EnglishGreeter {
    fun sayGoodMorning() {
        println("Good morning")
    }
}
Enter fullscreen mode Exit fullscreen mode

Next, we'll create a class to hold the greeters and invoke them:

@Service
class GreeterService(
    private val hebrewGreeter: HebrewGreeter = HebrewGreeter(),
    private val englishGreeter: EnglishGreeter = EnglishGreeter()
) {
    fun greetInAllLanguages() {
        hebrewGreeter.sayGoodMorning()
        englishGreeter.sayGoodMorning()
    }
}
Enter fullscreen mode Exit fullscreen mode

This implementation works as expected. However, imagine that we want to introduce a new greeter, such as a German greeter. In that case, we would need to modify the GreeterService class, which violates the open-closed principle from the SOLID principles.

To address this issue, we'll introduce an interface that encapsulates the common functionality of all greeters:

interface Greeter {
    fun sayGoodMorning()
}

@Component
class HebrewGreeter : Greeter {
    override fun sayGoodMorning() {
        println("בוקר טוב")
    }
}

@Component
class EnglishGreeter : Greeter {
    override fun sayGoodMorning() {
        println("Good morning")
    }
}

@Component
class GermanGreeter : Greeter {
    override fun sayGoodMorning() {
        println("Guten Morgen")
    }
}
Enter fullscreen mode Exit fullscreen mode

Now comes the exciting part. Since we've implemented an interface, the GreeterService can request Spring to inject all available implementations into its constructor:

@Service
class GreeterService(private val greeters: List<Greeter>) {
    fun greetInAllLanguages() {
        greeters.forEach { it.sayGoodMorning() }
    }
}
Enter fullscreen mode Exit fullscreen mode

Our code is now much simpler, but that's not the only benefit. If we introduce a new greeter in the future, it will be automatically added to the system without requiring any modifications to the code!

Another option instead of using List<Greeter> would be to use a Map<String, Greeter>. In this case, Spring would inject a map where the keys are the full class names and the values are the corresponding beans. For example, com.example.greeter.EnglishGreeter.


I hope you enjoyed this journey and learned something new. If you want to stay updated with my latest thoughts and ideas, feel free to register for my newsletter. You can also find me on LinkedIn or Twitter. Let's stay connected and keep the conversation going!

Top comments (0)