Spring Kotlin DSL: get all beans of certain type

2k Views Asked by At

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.

2

There are 2 best solutions below

0
On BEST ANSWER

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)
}
0
On

The ref function used in the DSL can be found here in the source of the framework. There is no equivalent for getting all beans of a type, but you could add your own extension to the BeanDefinitionDsl class to do this:

inline fun <reified T : Any> BeanDefinitionDsl.refAll() : Map<String, T> {
    return context.getBeansOfType(T::class.java)
}

Only problem is that the context required for this is internal in the currently released version of the framework. This commit from 8 days ago makes it publicly available "for advanced use-cases", but there hasn't been a new release of the framework since, so it's not available yet.

(The same commit also makes the class to extend directly the BeanDefinitionDsl class and not BeanDefinitionDsl.BeanDefinitionContext.)


Conclusion: you'll probably have to wait for the next release that includes the commit mentioned above, and then you'll be able to create this extension for yourself. I've also submitted a pull request in hopes that this could be included in the framework itself.