Search code examples
springspring-bootkotlinspring-kotlinspring-bean-dsl

Spring Kotlin DSL: get all beans of certain type


Suppose I have an interface Yoyo and different realizations of this interface:

interface Yoyo { 
    fun haha() {
        println("hello world")
    }
}

@Component class Yoyo1 : Yoyo
@Component class Yoyo2 : Yoyo
@Component class Yoyo3 : Yoyo
@Component class YoyoN : Yoyo

Now I would like to instantiate all beans and do some logic after the context has been initialized:

@SpringBootApplication
class YoyoApp

fun main(args: Array<String>) {
    SpringApplicationBuilder()
            .sources(YoyoApp::class.java)
            .initializers(beans {
               bean {
                   CommandLineRunner {
                       val y1 = ref<Yoyo1>()
                       val y2 = ref<Yoyo2>()
                       val y3 = ref<Yoyo3>()
                       val yN = ref<YoyoN>()
                       arrayOf(y1, y2, y3, yN).forEach { it.haha() }
                   }
               }
            })
            .run(*args)
}

Instead of manually getting ref to all beans (which is rather tedious), I would like to do this:

val list = ref<List<Yoyo>>()
list.forEach { it.haha() }

However I get an exception:

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.util.List<?>' available

I know I could do this instead, but I would like use the new Kotlin DSL instead:

@Component
class Hoho : CommandLineRunner {
    @Autowired
    lateinit var list: List<Yoyo>

    override fun run(vararg args: String?) {
        list.forEach { it.haha() }
    }
}

Is it possible? Any ideas?

P.S. Here is the gist.


Solution

  • The context mentioned in the previous answer by @zsmb13 was left internal in favor of the provider<Any>() function (starting with Spring 5.1.1). So in the end I ended up with the following:

    interface Yoyo {
        fun haha() {
            println("hello world from: ${this.javaClass.canonicalName}")
        }
    }
    
    @Component class Yoyo1 : Yoyo
    @Component class Yoyo2 : Yoyo
    @Component class Yoyo3 : Yoyo
    @Component class YoyoN : Yoyo
    
    
    @SpringBootApplication
    class YoyoApp
    
    fun main(args: Array<String>) {
        SpringApplicationBuilder()
            .sources(YoyoApp::class.java)
            .initializers(beans {
                bean {
                    CommandLineRunner {
                        val list = provider<Yoyo>().toList()
                        list.forEach { it.haha() }
                    }
                }
            })
            .run(*args)
    }