This is part two of a three-part series on dependency injection. Today’s topic covers dependency injection in OSGi. For an explanation of the dependency injection pattern and its benefits, please see part one. Part three will cover dependency injection in Spring.
OSGi is a standard for a modular component framework that allows JARs to be deployed and undeployed as “bundles” from an OSGi application at runtime. Each of these bundles contain modular “service” components that can have dependencies on other services, both within other bundles and within their own bundles as well (depending on the specifics of the OSGi framework chosen). Major OSGi implementations include Eclipse Equinox, Apache Felix, and Knopflerfish, each of which contain slightly different APIs for components that must interface directly with the framework.
Unfortunately, the OSGi specification does not provide robust dependency injection on its own. When a bundle is activated, a single activator class for the bundle is invoked that is responsible for being the “executive” for that bundle’s services, instantiating them and registering them with a central OSGi “service registry” singleton that knows which services are available. OSGi allows manual interactions with this service registry for publishing, unpublishing, and retrieving services from it at runtime. However, directly accessing the service registry tightly couples code to the chosen OSGi implementation’s APIs and does not completely solve the dependency injection problem. Manually written code is still required to instantiate concrete classes and wire them together; the problem has only been broken into one executive activation component per bundle with a central registry between them.
To address this issue, OSGi implementations are usually combined with a component model such as Gravity Service Binder, Declarative Services, Blueprint, or iPOJO to apply a robust dependency injection model on top of the existing bundle and service concepts. These component models use external configuration files or annotations to specify which services each bundle will instantiate, and which interfaces those services will provide and require. This removes the need for developers to write code that manually interacts with the OSGi service registry. Instead, these component models will automatically invoke the service registry on the developer’s behalf. A component model is added to the OSGi application as a bundle itself, and all bundles that wish to utilize it are added to the application through the component model bundle to enable automatic service registration capabilities.
OSGi component models generally allow services to specify the following properties:
– The service’s concrete class.
– Zero to many interfaces the service’s class implements that are publically exposed. Other services may specify these interfaces as required dependencies.
– Properties to set on the instantiated service (e.g., member variable or constructor values, depending on the component model).
– String filter properties that other services can use to uniquely target a specific service, such as a name field. For example, two different services that provide the same interface could be created from the same concrete class with different properties, and each would be discernible by its unique filter name. This name would allow them to be specifically targeted when required. Filter properties are only visible to the component model.
– Zero to many dependencies on other services.
Each dependency on another service generally has the following options:
– The required interface the dependency must provide. A setter method (or reflexive field injection) will be called to pass an instance of a service that implements this interface to the service requiring it.
– Filter properties that a candidate service with the specified required interface must meet. This allows services with specific properties and behaviors to be selected when multiple services that expose the same interface are present.
– How many instances of the required service are allowed (from zero to a specific number or a wildcard). If zero instances are required, the service can move to its “activated” state without obtaining any references to the dependency. Otherwise, the component cannot become activated until all of its dependencies are met.
Since OSGi allows bundles to be added and removed at runtime, services that are utilized as dependencies by other services may become unavailable while the application is executing. When this occurs, an un-setter method is called on all services that have a reference to the service being deactivated, which are then deactivated themselves if their removed dependency was not optional (e.g. the dependency had a required cardinality of more than zero). All services are required to implement deactivation methods to properly close their resources when they are asked to shut down, which are called when their dependencies can no longer be satisfied or when they are shut down due to their own bundle’s deactivation. Once another service becomes available that satisfies the removed dependency, then services that required it are activated again.
One trick that can be performed with runtime bundle deployment is adding a new service that provides the same interface and filters as an existing service before deactivating the existing service. The addition of the new service will not affect any currently activated components since they will already be using the old service (assuming they are not configured to consume multiples of this dependency). Once the old service is removed, it will be un-set from all services that require it, only to be quickly replaced by the new service. This can be used to upgrade code while an application is running without shutting it down, assuming the system can handle a momentary absence of a set of services (generally via a queuing mechanism to hold on to requests until a service is available again).
Blueprint is especially well-suited for quick service swaps. Unlike other OSGi component models, Blueprint never gives services direct references to other services. Instead, it provides services with proxies to their required services that queue method invocation calls until a service activates that satisfies the dependency, at which time it forwards the invocations on to the recipient. This allows services to make calls to their dependencies without fear of the dependency being null due to deactivation if the service was not shut down due to the loss of the dependency, and it allows services to run even when all of their dependencies are not met (for a specified period of time). This can greatly simplify service code by removing null checks for dependencies in addition to reducing service downtime.
The iPOJO component model also uses proxies instead of direct service references, but for a slightly different purpose. iPOJO allows dependencies to be marked as temporal, which allows services to activate without resolving those dependencies. References to unresolved temporal dependencies will block the calling thread in the service requiring them for a set period of time; if the dependency becomes available within that period of time, the thread can continue and will invoke it. iPOJO also uses proxies to help enrich POJOs that have no OSGi-specific imported classes or annotations become able to be used as services at runtime.
Because services cannot be activated until they obtain references to activated services that satisfy their required dependencies, circular service references can cause issues depending on the component framework selected. Unless a component model such as Blueprint or iPOJO provides proxies in lieu of direct references to required services, circular service dependencies will prevent all involved services from being able to activate.
The following list summarizes major differences between the main OSGi component models.
Gravity Service Binder:
– Configuration: XML referenced from each bundle JAR’s manifest file.
– Service activation: Eager instantiation – services run as soon as their dependencies are met.
– Code impact: A GSB-specific class is required in service constructors, and activate/deactivate methods inherited from a GSB-specific interface are required, which tightly couples service code to GSB classes. Setter/unsetter methods for each dependency are also required. Requiring GSB-specific classes makes GSB a worse solution than Blueprint, iPOJO, and Spring (as covered in the next part of this blog series) with respect to code reuse outside of a GSB-based framework.
– Configuration: Declarative Services-specific service annotations that generate XML on compilation, or XML referenced from each bundle JAR’s manifest file.
– Service activation: Lazy activation by default – a service isn’t activated until another service tries to use it. Eager activation can also be enabled.
– Code impact: Declarative Services-specific arguments are present in activation and deactivation methods. Using annotations will introduce coupling to Declarative Services-specific classes. As such, all Declarative Services services are tightly coupled to Declarative Services classes. This makes Declarative Services a worse solution than Blueprint, iPOJO, and Spring (as covered in the next part of this blog series) with respect to code reuse outside of a Declarative Services-based framework.
– Configuration: Blueprint-specific service annotations or XML referenced from each bundle JAR’s manifest file.
– Service activation: Both lazy and eager activation are available. Blueprint gives services proxies of their dependencies instead of direct references. This allows services to activate and queue requests before their dependencies activate, and enables circular dependency references. As a side-effect of Blueprint’s implementation of proxies, services are not allowed to have dependencies on other services within the same bundle. Prototype-scoped services can be created, which provide a unique instantiation to each service that requires them.
– Code impact: Blueprint XML configuration can specify activation/deactivation and setter methods via external configuration without requiring service classes to import Blueprint classes. However, if annotations are used, coupling to Blueprint-specific classes packages will occur.
– Configuration: iPOJO-specific service annotations or XML referenced from each bundle JAR’s manifest file.
– Service activation: Both lazy and eager activation are available. Circular dependencies are possible due to iPOJO’s use of proxies when satisfying dependencies. Transactional dependences can be created, which allow services to start before their dependencies are met and block on calls to unmet dependencies until they are resolved.
– Code impact: iPOJO XML configuration can specify activation/deactivation methods, setter methods, and direct field injection without setter methods via external configuration without requiring service classes to import iPOJO classes. However, if annotations are used, coupling to iPOJO-specific classes will occur.
The next entry in this series will compare dependency injection with OSGI to dependency injection with Spring. Stay connected with us via LinkedIn and Twitter at @CrederaOpen for further updates on this series and other Open Technologies insights. Please use the comments section below or email us at firstname.lastname@example.org if you have any questions.