Dependency Injection
klyn.di discovers application components, resolves typed property dependencies, and
controls initialization and destruction. The container remains explicit: the application creates,
scans, uses, and closes its DIContext.
| Element | Role |
|---|---|
@Component | Declares a general container-managed class. |
@Service | Declares an application-service component. |
@Autowired | Marks a property that must receive a dependency. |
@InitMethod | Marks a zero-argument method invoked after injection. |
@DestroyMethod | Marks a zero-argument method invoked before destruction. |
DIContext | Owns registration, discovery, injection, and lifecycle. |
The current container injects properties annotated with @Autowired. Components must
provide a zero-argument constructor. Constructor parameters and unannotated properties are not
injected implicitly.
Keep interfaces and public types in their own matching source files. In this example, the class-loader
root contains the package inventory.services.
# inventory/services/StockService.kn
package inventory.services
public interface StockService:
public available(productId as Int) as Int
# inventory/services/StockServiceImpl.kn
package inventory.services
import klyn.di
@Service(name="stockService")
public class StockServiceImpl implements StockService:
public available(productId as Int) as Int:
return 12
The annotated property retains its static interface type. The context injects an assignable component; application code does not depend on the implementation class.
# inventory/services/InventoryReport.kn
package inventory.services
import klyn.di
@Component(name="inventoryReport")
public class InventoryReport:
@Autowired
public property stockService as StockService
public printAvailability(productId as Int) as Void:
print(this.stockService.available(productId))
Resolution first checks a registered component whose name matches the property name. If none exists, the context searches for exactly one component assignable to the property's declared type. No match or several matches is an error; the container never chooses an arbitrary implementation.
import inventory.services
import klyn.di
context = DIContext(packages=["inventory.services"])
try:
context.scanAnnotations()
report = context.getComponent("inventoryReport") as InventoryReport
report.printAvailability(42)
finally:
context.close()
Package filters keep startup deterministic and avoid scanning unrelated application types.
scanAnnotations() loads those packages, creates every discovered component, registers all
instances, injects dependencies, then runs initialization callbacks.
package inventory.services
import klyn.di
@Service(name="catalogCache")
public class CatalogCache:
private _ready as Boolean = false
@InitMethod
public start() as Void:
this._ready = true
@DestroyMethod
public stop() as Void:
this._ready = false
Initialization runs only after every discovered instance has been registered and dependencies have
been injected. close() invokes destruction callbacks in reverse creation order and still
attempts the remaining callbacks if one fails.
Existing objects can be registered directly, and reflected types can be created through the context. These two operations deliberately have different ownership rules.
import inventory.services
import klyn.di
context = DIContext()
try:
external = CatalogCache()
context.registerComponent("external", external)
cache = context.createComponent(CatalogCache.type, "cache") as CatalogCache
assert context.getComponent("cache") is cache
finally:
context.close()
registerComponent()stores a caller-owned instance; the context does not run its destroy callback.createComponent()invokes the zero-argument constructor, injects properties, runs initialization, and owns destruction.destroyComponent()destroys and unregisters one managed component by name.
Treat a failed context startup as an application configuration error. Missing dependencies raise
ComponentNotFoundException; duplicate registration names, invalid lifecycle methods, and
ambiguous typed dependencies also fail instead of silently producing a partial graph.
Prefer constructor arguments for ordinary application values and explicit method parameters for
transient data. Register long-lived services whose lifecycle and substitutability benefit from a
container; do not hide every object lookup behind getComponent().