0
# Advanced Features
1
2
Advanced dependency injection patterns including constructor injection, external sources, binding search, sub-DI creation, and specialized utilities for complex dependency management scenarios.
3
4
## Capabilities
5
6
### Constructor Injection (New Operator)
7
8
Automatic constructor parameter injection for creating instances with dependency resolution without explicit binding definitions.
9
10
```kotlin { .api }
11
/**
12
* Create instance with no-parameter constructor injection
13
* @param constructor Constructor function reference
14
* @return New instance with all dependencies automatically injected
15
*/
16
fun <T> DirectDIAware.new(constructor: () -> T): T
17
18
/**
19
* Create instance with 1-parameter constructor injection
20
* @param constructor Constructor function reference
21
* @return New instance with P1 automatically injected via instance<P1>()
22
*/
23
fun <P1, T> DirectDIAware.new(constructor: (P1) -> T): T
24
25
/**
26
* Create instance with 2-parameter constructor injection
27
* @param constructor Constructor function reference
28
* @return New instance with P1, P2 automatically injected
29
*/
30
fun <P1, P2, T> DirectDIAware.new(constructor: (P1, P2) -> T): T
31
32
/**
33
* Create instance with 3-parameter constructor injection
34
* @param constructor Constructor function reference
35
* @return New instance with P1, P2, P3 automatically injected
36
*/
37
fun <P1, P2, P3, T> DirectDIAware.new(constructor: (P1, P2, P3) -> T): T
38
39
// ... continues up to 22 parameter overloads
40
41
/**
42
* Exception thrown when a constructor parameter is not used in injection
43
* This helps detect unused parameter instances in new operator calls
44
*/
45
class DI.UnusedParameterException(message: String, cause: Exception? = null) : RuntimeException(message, cause)
46
```
47
48
### External Sources
49
50
Fallback mechanism for dependency resolution when bindings are not found in the container, enabling integration with other DI frameworks or dynamic resolution.
51
52
```kotlin { .api }
53
/**
54
* External source interface for fallback dependency resolution
55
*/
56
interface ExternalSource {
57
/**
58
* Attempt to provide a factory for the given key when not found in container
59
* @param key The binding key that was not found
60
* @param context The resolution context
61
* @return Factory function or null if this source cannot provide the dependency
62
*/
63
fun <C : Any, A, T : Any> getFactory(
64
key: DI.Key<C, A, T>,
65
context: C
66
): ((A) -> T)?
67
}
68
69
/**
70
* External sources list for registering fallback resolution mechanisms
71
* External sources are consulted in order when bindings are not found
72
*/
73
val DI.MainBuilder.externalSources: MutableList<ExternalSource>
74
```
75
76
### Sub-DI Creation
77
78
Creation of child DI containers that inherit from parent containers while allowing additional bindings and overrides.
79
80
```kotlin { .api }
81
/**
82
* Create a sub-DI container with additional bindings
83
* @param allowSilentOverride Whether to allow implicit binding overrides
84
* @param copy Copy strategy for inheriting bindings from parent
85
* @param init Configuration block for additional bindings
86
* @return New DI container extending the current one
87
*/
88
fun DirectDIAware.subDI(
89
allowSilentOverride: Boolean = false,
90
copy: Copy = Copy.NonCached,
91
init: DI.MainBuilder.() -> Unit
92
): DI
93
94
/**
95
* Copy strategies for sub-DI creation
96
*/
97
sealed class Copy {
98
/** Copy no bindings from parent */
99
object None : Copy()
100
101
/** Copy all bindings from parent */
102
object All : Copy()
103
104
/** Copy only non-cached bindings (providers, factories) */
105
object NonCached : Copy()
106
}
107
```
108
109
### Factory Currying
110
111
Utilities for converting factory functions into provider functions by pre-supplying arguments.
112
113
```kotlin { .api }
114
/**
115
* Convert a factory function to a provider by currying with an argument
116
* @param arg Function that provides the argument for the factory
117
* @return Provider function that calls the factory with the supplied argument
118
*/
119
fun <A, T> ((A) -> T).toProvider(arg: () -> A): () -> T
120
```
121
122
### Binding Search and Discovery
123
124
Advanced binding search capabilities for finding and inspecting bindings within the DI container.
125
126
```kotlin { .api }
127
/**
128
* Specifications for searching bindings in the container
129
*/
130
data class SearchSpecs(
131
val contextType: TypeToken<*>? = null,
132
val argType: TypeToken<*>? = null,
133
val type: TypeToken<*>? = null,
134
val tag: Any? = null
135
)
136
137
/**
138
* DSL for building search specifications
139
*/
140
class SearchDSL {
141
/**
142
* Search for bindings with specific context type
143
*/
144
fun <C : Any> context(type: TypeToken<C>)
145
146
/**
147
* Search for bindings with specific argument type
148
*/
149
fun <A> argument(type: TypeToken<A>)
150
151
/**
152
* Search for bindings with specific return type
153
*/
154
fun <T> type(type: TypeToken<T>)
155
156
/**
157
* Search for bindings with specific tag
158
*/
159
fun tag(tag: Any)
160
}
161
162
/**
163
* DSL for finding bindings in the container
164
*/
165
class FindDSL : SearchDSL() {
166
// Additional methods for finding specific binding patterns
167
}
168
169
/**
170
* Find all bindings matching the search criteria
171
* @param f DSL block for specifying search criteria
172
* @return List of matching bindings with their keys and context matches
173
*/
174
fun DITree.findAllBindings(
175
f: FindDSL.() -> Unit
176
): List<Triple<DI.Key<*, *, *>, DIBinding<*, *, *>, ContextMatch>>
177
178
/**
179
* Context match information for search results
180
*/
181
enum class ContextMatch {
182
EXACT, // Exact context type match
183
SUPER, // Context is a supertype
184
SUB // Context is a subtype
185
}
186
```
187
188
### Late Initialization Support
189
190
Patterns and utilities for delayed DI initialization in scenarios where immediate container creation is not possible.
191
192
```kotlin { .api }
193
/**
194
* Late-initialized DI container for manual setup scenarios
195
*/
196
class LateInitDI : DI {
197
/**
198
* Base DI instance that must be set before use
199
* @throws UninitializedPropertyAccessException if accessed before initialization
200
*/
201
lateinit var baseDI: DI
202
203
// All DI operations delegate to baseDI after initialization
204
}
205
206
/**
207
* Property delegate for late-initialized DI-aware properties
208
*/
209
class LateinitDIProperty<T>(
210
private val getDI: () -> DI,
211
private val creator: DirectDI.() -> T
212
) : LazyDelegate<T>
213
```
214
215
### Multi-Argument Support
216
217
Advanced binding patterns for functions and factories that require multiple arguments beyond single-parameter factories.
218
219
```kotlin { .api }
220
/**
221
* Multi-argument factory binding support
222
* Allows binding factories that take multiple typed arguments
223
*/
224
interface MultiArgFactory<T> {
225
fun with(vararg args: Any): T
226
}
227
228
/**
229
* Multi-argument binding DSL support
230
*/
231
fun <T : Any> DI.Builder.multiArgFactory(
232
creator: DirectDI.(Array<Any>) -> T
233
): MultiArgFactory<T>
234
```
235
236
### Error Information Enhancement
237
238
Enhanced error reporting with detailed container information and binding diagnostics.
239
240
```kotlin { .api }
241
/**
242
* Enhanced exception with detailed binding information
243
*/
244
class DI.NotFoundException(
245
val key: Key<*, *, *>,
246
message: String
247
) : RuntimeException(message) {
248
/** The binding key that was not found */
249
val key: Key<*, *, *>
250
}
251
252
/**
253
* Exception for dependency cycles with detailed loop information
254
*/
255
class DI.DependencyLoopException(message: String) : RuntimeException(message)
256
257
/**
258
* Exception for binding override conflicts
259
*/
260
class DI.OverridingException(message: String) : RuntimeException(message)
261
262
/**
263
* Exception for search operations that return no results
264
*/
265
class DI.NoResultException(
266
val search: SearchSpecs,
267
message: String
268
) : RuntimeException(message)
269
270
// Error configuration for detailed diagnostics
271
var DI.MainBuilder.fullDescriptionOnError: Boolean
272
var DI.MainBuilder.fullContainerTreeOnError: Boolean
273
```
274
275
### Type System Integration
276
277
Advanced type handling and generic type preservation for complex dependency scenarios.
278
279
```kotlin { .api }
280
/**
281
* Typed wrapper for values with their type information
282
* @param A Type of the wrapped value
283
*/
284
interface Typed<A> {
285
/** TypeToken representing the type of the value */
286
val type: TypeToken<A>
287
288
/** The actual typed value */
289
val value: A
290
}
291
292
/**
293
* Create a typed wrapper with immediate value
294
* @param type TypeToken for the value type
295
* @param value The value to wrap
296
* @return Typed wrapper containing the value and its type
297
*/
298
fun <A> Typed(type: TypeToken<A>, value: A): Typed<A>
299
300
/**
301
* Create a typed wrapper with lazy value evaluation
302
* @param type TypeToken for the value type
303
* @param func Function that provides the value when needed
304
* @return Typed wrapper with lazy value evaluation
305
*/
306
fun <A> Typed(type: TypeToken<A>, func: () -> A): Typed<A>
307
```
308
309
### Container Inspection and Debugging
310
311
Utilities for inspecting and debugging DI container state and binding resolution.
312
313
```kotlin { .api }
314
/**
315
* Container tree interface for inspecting binding structure
316
*/
317
interface DITree {
318
/**
319
* Find bindings matching search criteria
320
*/
321
fun find(search: SearchSpecs): List<DI.Key<*, *, *>>
322
323
/**
324
* Get all registered bindings
325
*/
326
fun allBindings(): Map<DI.Key<*, *, *>, DIBinding<*, *, *>>
327
328
/**
329
* Get container description for debugging
330
*/
331
fun containerDescription(): String
332
}
333
334
/**
335
* Binding information for diagnostics
336
*/
337
data class BindingInfo(
338
val key: DI.Key<*, *, *>,
339
val binding: DIBinding<*, *, *>,
340
val scope: String?,
341
val description: String
342
)
343
```
344
345
**Usage Examples:**
346
347
```kotlin
348
// Constructor injection with new operator
349
class OrderService : DirectDIAware {
350
override val directDI: DirectDI = appDirectDI
351
352
fun processOrder(order: Order) {
353
// Automatic constructor parameter injection
354
val validator = new(::OrderValidator) // Injects dependencies automatically
355
val processor = new(::PaymentProcessor) // Finds and injects required services
356
val emailer = new(::OrderEmailService) // Injects email service and templates
357
358
validator.validate(order)
359
val payment = processor.process(order.paymentInfo)
360
emailer.sendConfirmation(order, payment)
361
}
362
363
// Multi-parameter constructor injection
364
fun createComplexService() {
365
val service = new { db: Database, cache: Cache, logger: Logger, config: Config ->
366
ComplexService(db, cache, logger, config) // All 4 params auto-injected
367
}
368
}
369
}
370
371
// External sources for fallback resolution
372
class SpringIntegrationSource : ExternalSource {
373
private val springContext: ApplicationContext = getSpringContext()
374
375
override fun <C : Any, A, T : Any> getFactory(
376
key: DI.Key<C, A, T>,
377
context: C
378
): ((A) -> T)? {
379
return try {
380
val bean = springContext.getBean(key.type.jvmType as Class<T>)
381
{ _ -> bean } // Return factory that ignores argument and returns Spring bean
382
} catch (e: NoSuchBeanDefinitionException) {
383
null // Let DI continue looking
384
}
385
}
386
}
387
388
val integratedDI = DI {
389
// Regular Kodein-DI bindings
390
bind<UserService>() with singleton { UserServiceImpl() }
391
392
// Add Spring as fallback
393
externalSources.add(SpringIntegrationSource())
394
395
// Now can inject Spring beans even if not bound in Kodein-DI
396
}
397
398
// Sub-DI for testing and isolation
399
class TestingService : DirectDIAware {
400
override val directDI = productionDI.direct
401
402
fun runTestScenario() {
403
// Create test-specific DI with mocks
404
val testDI = subDI(copy = Copy.NonCached) {
405
bind<EmailService>(overrides = true) with singleton { MockEmailService() }
406
bind<PaymentProcessor>(overrides = true) with singleton { TestPaymentProcessor() }
407
408
// Additional test-only services
409
bind<TestDataProvider>() with singleton { TestDataProviderImpl() }
410
}
411
412
// Use test DI for this scenario
413
val testDirectDI = testDI.direct
414
val service = testDirectDI.new(::OrderService) // Uses mocked dependencies
415
service.processTestOrder()
416
}
417
}
418
419
// Factory currying for partial application
420
class DataProcessingService : DirectDIAware {
421
override val directDI = appDirectDI
422
423
fun setupProcessors() {
424
// Get factory and curry it with configuration
425
val processorFactory = directDI.factory<ProcessingConfig, DataProcessor>()
426
427
val standardConfig = ProcessingConfig(threads = 4, batchSize = 100)
428
val standardProcessor = processorFactory.toProvider { standardConfig }
429
430
val highPerfConfig = ProcessingConfig(threads = 8, batchSize = 200)
431
val highPerfProcessor = processorFactory.toProvider { highPerfConfig }
432
433
// Now have two providers with different configurations
434
processStandardData(standardProcessor())
435
processHighVolumeData(highPerfProcessor())
436
}
437
}
438
439
// Binding search and discovery
440
class DIInspector(private val di: DI) {
441
fun inspectBindings() {
442
val tree = di.container.tree
443
444
// Find all singleton bindings
445
val singletons = tree.findAllBindings {
446
// Search by binding type patterns
447
}.filter { (_, binding, _) ->
448
binding.javaClass.simpleName.contains("Singleton")
449
}
450
451
println("Found ${singletons.size} singleton bindings:")
452
singletons.forEach { (key, binding, match) ->
453
println(" ${key.description} -> ${binding.description}")
454
}
455
456
// Find all bindings for a specific type
457
val userServiceBindings = tree.find(SearchSpecs(
458
type = generic<UserService>(),
459
tag = null
460
))
461
462
println("UserService bindings: $userServiceBindings")
463
}
464
465
fun diagnosticReport(): String {
466
return try {
467
di.container.tree.containerDescription()
468
} catch (e: Exception) {
469
"Error generating diagnostic report: ${e.message}"
470
}
471
}
472
}
473
474
// Late initialization patterns
475
class ApplicationContext {
476
private val lateInitDI = LateInitDI()
477
478
// Services that depend on DI
479
private val userService: UserService by lateInitDI.instance()
480
private val configService: ConfigService by lateInitDI.instance()
481
482
fun initialize(config: AppConfig) {
483
// Set up DI after configuration is available
484
lateInitDI.baseDI = DI {
485
bind<AppConfig>() with instance(config)
486
bind<UserService>() with singleton { UserServiceImpl(instance()) }
487
bind<ConfigService>() with singleton { ConfigServiceImpl(instance()) }
488
}
489
490
// Now property delegates can resolve
491
configService.loadConfiguration()
492
userService.initializeUserData()
493
}
494
}
495
496
// Advanced error handling and diagnostics
497
class RobustDIService : DIAware {
498
override val di = DI {
499
fullDescriptionOnError = true // Include full type names in errors
500
fullContainerTreeOnError = true // Include all bindings in NotFoundException
501
502
bind<PrimaryService>() with singleton { PrimaryServiceImpl() }
503
bind<FallbackService>() with singleton { FallbackServiceImpl() }
504
}
505
506
fun robustOperation() {
507
try {
508
val service: PrimaryService by instance()
509
service.execute()
510
} catch (e: DI.NotFoundException) {
511
println("Binding not found: ${e.key.fullDescription}")
512
println("Available bindings:")
513
println(e.message) // Contains full container tree due to config
514
515
// Try fallback
516
val fallback: FallbackService by instance()
517
fallback.execute()
518
} catch (e: DI.DependencyLoopException) {
519
println("Dependency loop detected: ${e.message}")
520
// Handle circular dependency
521
}
522
}
523
}
524
525
// Multi-argument and complex binding scenarios
526
val advancedDI = DI {
527
// Multi-argument factory using array
528
bind<ReportGenerator>() with factory { args: Array<Any> ->
529
val format = args[0] as ReportFormat
530
val filters = args[1] as List<DataFilter>
531
val options = args[2] as ReportOptions
532
ReportGeneratorImpl(format, filters, options)
533
}
534
535
// Typed wrapper for preserving generic information
536
bind<Typed<List<User>>>() with singleton {
537
Typed(generic<List<User>>()) {
538
userRepository.getAllUsers()
539
}
540
}
541
}
542
```