Explain Spring's Kotlin bean-definition DSL (the beans { } block). How is it structured, how does it wire dependencies, and how do you plug it into an application?
answer
- beans { } -> BeanDefinitionDsl : ApplicationContextInitializer
- bean<T>() autowires; bean { T(ref()) } supplies
- ref() / ref("name") resolve dependencies
- profile { } and env[...] gating
- addInitializers in runApplication, before refresh
basics
~20 sThe beans { } DSL builds bean definitions in Kotlin without annotations. Inside it you write bean<Foo>() to register a type, or bean { Bar(ref()) } to construct one and pull dependencies with ref(). It returns a BeanDefinitionDsl you initialize against a GenericApplicationContext.
solid answer
~40 sThe Kotlin DSL is a type-safe lambda: `beans { ... }` returns a `BeanDefinitionDsl`, which is an `ApplicationContextInitializer<GenericApplicationContext>`. Inside the block, `bean<Foo>()` registers Foo and lets the container autowire its constructor; `bean { Bar(ref(), ref<Baz>()) }` provides an explicit instance supplier where `ref()` resolves a dependency by type (or `ref("name")` by name) from the context. You can set scope with `bean(scope = Scope.PROTOTYPE)`, mark primary/lazy, and gate registration with `profile("prod") { ... }` or read `env["key"]`. To apply it, either register it as a Spring Boot `ApplicationContextInitializer` (via `addInitializers`/`context.initializer`) or call `dsl.initialize(genericContext)` before refresh. It is the idiomatic Kotlin equivalent of repeated registerBean calls — reflection-light and AOT-friendly.
code
kotlin · 22 linesval myBeans = beans {
bean<OrderRepository>() // autowired constructor
bean { OrderService(ref()) } // explicit supplier + ref()
bean(name = "auditClient", isLazyInit = true) {
AuditClient(env["audit.url"] ?: "http://localhost")
}
profile("prod") {
bean<MetricsExporter>()
}
}
// Wire into Spring Boot
fun main(args: Array<String>) {
runApplication<App>(*args) {
addInitializers(myBeans)
}
}
// Or into a plain context
// val ctx = GenericApplicationContext()
// myBeans.initialize(ctx)
// ctx.refresh()go deeper
Recognize beans { bean<Foo>() } as a Kotlin way to declare beans without annotations.
Explain bean<T>() vs bean { T(ref()) }, ref() resolution, and applying via addInitializers.
Cover profiles/env gating, scope/lazy/primary options, and that it is an ApplicationContextInitializer applied before refresh.
Position it in Spring's functional/AOT-native direction and reason about reflection-free construction vs autowiring trade-offs in a Kotlin codebase.
**What it is** Spring provides a Kotlin DSL (in `org.springframework.context.support`) that wraps functional registration in idiomatic Kotlin. The entry function is: ```kotlin fun beans(init: BeanDefinitionDsl.() -> Unit): BeanDefinitionDsl ``` `BeanDefinitionDsl` implements `ApplicationContextInitializer<GenericApplicationContext>`. So a `beans { }` block is essentially a reusable initializer that, when applied to a context, registers all the beans declared inside it — using the same `registerBean`/instance-supplier machinery underneath. **Declaring beans** - `bean<Foo>()` — registers `Foo`; the container instantiates it and **autowires the constructor** (resolves each parameter from the context). Reflection-based like a normal bean, but declared functionally. - `bean { Foo(ref(), ref<Bar>("barName")) }` — supplies an explicit **instance supplier** lambda. Inside it, `ref<T>()` resolves a bean by type and `ref<T>("name")` by name from the enclosing context. This mirrors `registerBean(Class, Supplier)` — you construct the object yourself. - Named/typed options: `bean<Foo>(name = "myFoo", scope = BeanDefinitionDsl.Scope.PROTOTYPE, isLazyInit = true, isPrimary = true, isAutowireCandidate = false)`. **Environment & profiles** - `profile("cloud") { bean<CloudClient>() }` registers the inner beans only when the profile is active. - `env` gives access to the `Environment`: `bean { Config(env["app.token"]) }`. - `provider<T>()` yields an `ObjectProvider<T>` for lazy/optional/multi resolution inside a supplier. **Plugging it in** (three routes) 1. **Spring Boot `ApplicationContextInitializer`**: because `BeanDefinitionDsl` *is* one, register it: `runApplication<App>(*args) { addInitializers(beans { ... }) }` or list it in `context.initializer.classes`/`META-INF/spring.factories`. 2. **Plain context**: `val ctx = GenericApplicationContext(); beans { ... }.initialize(ctx); ctx.refresh()`. 3. As a function you compose and pass around, enabling modular bean sets. **Why use it** - Idiomatic, concise Kotlin; no annotation processing or scanning. - Explicit wiring reads top-to-bottom. - AOT/GraalVM-native friendly — favored in the functional Kotlin style Spring promotes. **Gotchas** - `ref()` resolves at bean-creation time from the context; a missing/ambiguous bean fails then, not at DSL-declaration time. - The DSL must be applied **before** `refresh()` (initializers run before refresh by design). - `bean<Foo>()` autowires (reflection) whereas `bean { Foo(ref()) }` uses your supplier — pick based on whether you want reflection-free construction. - It is not annotation-discoverable; component scanning won't see these beans, and vice versa (though both can coexist in one context). - Type inference: `ref()` needs enough context to infer T; specify `ref<Bar>()` when ambiguous.
- What is the difference between bean<Foo>() and bean { Foo(ref()) }?bean<Foo>() lets the container instantiate and autowire Foo's constructor (reflection). bean { Foo(ref()) } supplies an explicit instance-supplier lambda where you construct Foo and resolve deps via ref() — reflection-free construction.
- How does a beans { } block get applied to a real Spring Boot app?BeanDefinitionDsl implements ApplicationContextInitializer<GenericApplicationContext>, so you pass it to addInitializers in runApplication (or register it as a context initializer). Initializers run before refresh, so the beans are present at startup.
saying these in an interview costs you the question
- Saying beans { } is annotation-based or needs @Configuration
- Thinking ref() resolves at declaration time rather than creation time
- Not knowing BeanDefinitionDsl is an ApplicationContextInitializer
- Claiming component scanning discovers DSL-declared beans