The core of KOS consists of many services that provide a broad range of functionality. Many of these services also leverage listener interfaces to distribute system events. Many developer components need access to services or implement listener interfaces. Like many modern frameworks, KOS provides dependency injection to simplify the most common developer use cases.
Dependency injection is a mechanism by which a developer creates components that depend on other components, but rather than explicitly connecting all the components in code, the dependencies are described, and the system connects them all automatically. This can drastically simplify the development of complex systems as developers can focus on core functionality and let the system connect everything. KOS provides a dependency injection framework similar to popular frameworks but with unique features suited for device development.
BeanContext
At the core of the KOS dependency injection framework is the BeanContext class. A context is a container holding Java beans that participate in dependency injection. Unlike most frameworks, which require beans to be created by the framework, developers can put any Java beans into a BeanContext to have them participate in dependency injection.
Contexts can also have parent and child contexts, which allow for scoped dependency injection, pulling some dependencies from a shared parent pool of objects while keeping other objects isolated. For example, each KOS application is backed by a BeanContext for application-specific dependency injection while inheriting any system-wide beans from the parent KOS context.
Autowiring
The most commonly used dependency injection feature is autowiring. When a user-developed component needs access to services, it creates a class variable and annotates it with @Autowired. The KOS dependency injection framework will inject the actual service object into the class variable automatically during initialization.
Similarly, when using interfaces for listener callbacks, developers can annotate a ListenerList class variable with @Autowired, and the framework will automatically inject any beans in the context that implement the associated interface into the list.
Autowiring is a common feature in many dependency injection frameworks and works as expected in KOS.
Determining Readiness
One unique aspect of the KOS dependency injection framework is the concept of readiness. While most frameworks provide a lifecycle event to indicate that all autowiring is complete on an object, they don’t provide any support to indicate readiness. For example, consider a service that needs to read information from a physical piece of hardware before it can process any requests, but that hardware can take a variable amount of time to boot and become available for access. If a framework indicates that autowiring is complete, components that depend on this service have no standard way to know that this service, while dependency injected, is not usable yet.
KOS solves this by introducing a concept of readiness via the Ready and ReadyListener interfaces. These interfaces provide a mechanism to indicate when a component is ready for use. The KOS dependency injection framework uses this as part of the dependency injection process to also manage the readiness of components in the context. Components don’t need to do anything special to leverage this. By simply implementing the Ready interface, the framework can compute the dependency graph of beans and only mark them ready once all their dependencies become ready, and the bean itself indicates it is ready. As many components in device development require additional work beyond bean creation to be usable, this guarantees all components initialize in a controlled and deterministic way without specific developer intervention.
