Context & Autowiring

In KOS, a context is a container that holds objects (beans) that other components that may need access to. KOS uses dependency injection, so components do not have to manually locate their dependencies, instead these dependencies are supplied automatically. Objects that are being fetched for dependency injection can be annotated by @Autowired. Objects must be placed in the context for them to get autowired. Note, the instance placed in the context is the one retrieved through autowiring, ensuring predictable behavior across the system.

Context Hierarchy in KOS

KOS is an application based system, where each application gets its own context. These contexts are arranged in a hierarchy: KOS core (root) context → system app context → other app contexts. Child contexts can access beans from parent contexts, but generally parents cannot access children contexts. For example, any applications can access KOS core services like ApplicationService and TroubleService.

Contexts at the same level cannot access each other’s contents. Siblings are isolated by design, encouraging modularity.

Two-Way Autowiring for Listeners

Typically, listening to events requires manual lifecycle management: you must find the object you want to listen to (often in a parent context), register yourself as a listener, and unregister yourself later. In an application based system this is especially difficult as listeners must dynamically register and unregister as apps load and unload. If you forget to unregister it will result in memory leaks.

KOS solves this with ListenerList<T>, which extends AutowiredList<T>. This is a list that automatically registers listeners (any object of type T) when they appear in any context (parent, current, or child) and automatically unregisters the listener when their context is destroyed. Thus, you simply have to implement T to be a listener, and autowiring handles everything else in the background.

Additionally, a ListenerList provide callbacks when items are added or removed, although the objects may not be fully ready when this callback happens.

What and When to add to the context?

Objects that need to be globally accessible should be added to the application context. Typically, these are services (or managers), that is singleton objects that are used throughout the app. With the exception of listeners, you generally should not add multiple instances of a class in a context. For example, adding individual Pump instances would create ambiguity about which pump another component should use. Instead, you would add a PumpService that manages Pump instances. Components that need to trigger a pump can make a call to the service to do so.

When are beans autowired?

If beans are added to the context in the load() lifecycle event of an application, they are automatically autowired. But, if objects are added later, the user must manually call update() on the context to autowire the newly added objects. Multiple updates are allowed and are safe.

How to use Autowired objects: Readiness

Autowiring solves discovery, but it does not guarantee that dependencies are ready to be used. For example,PumpService may autowire TroubleService and try to create a Trouble immediately. However,TroubleService has yet to set up its own backend storage and controller structures. So, using dependencies too early may fail.

To solve this, KOS uses the concept of readiness. Every object can indicate when it is fully set up and ready to be used. A service only marks itself Ready once all of its attributes are initialized, and dependent classes can wait for this before doing anything that relies on the service. See the Ready article for more details: link.

Scenario 1: Object is not in context, but needs Services

Sometimes an object needs access to a service without being in the context itself. For example, a Pump instance needs to log troubles using TroubleService, but it isn’t a singleton and shouldn’t be placed in the context. In this case, you can connect() the object to the context. This gives the object access to services in that context without actually adding the object to it.

Scenario 2: Sub-Contexts for multiple instances of a Service

Let’s say you have a device with two independent screens, each handling its own set of tasks and data. They behave the same way, but each screen needs its own instance of ScreenService, along with its own internal state, active operations, and so on. You can’t use a single global ScreenService to manage both. In this case, you can create sub-contexts inside your app—one per screen. Each sub-context holds the services tied to that particular screen, while the main app context holds services that can be used by all screens. All sub-contexts still inherit parent services as usual, and they follow the same hierarchical rules, with the context in which they are created acting as their parent.